Forum: Mikrocontroller und Digitale Elektronik char an String anhängen


von Lugge (Gast)


Lesenswert?

Hallo,

ich stehe hier grad bisl aufn Schlauch, obwohl die Lösung wohl ganz 
einfach ist.
Sufu wurde natürlich bemüht. Spuckt auch einiges aus, nur ist mein 
Problem leicht anders.

Ich habe eine Funktion die im RX Interrupt eines UART Moduls aufgerufen 
wird.
Außerdem liegt im globalen Speicher eine Zeichenkette mit Platz für 19 
Zeichen.

char myString[20]

Die Funktion soll mir nun das empfangene Zeichen anhängen. Bei Empfang 
eines Abschlusszeichens 'CR' soll der String terminiert werden.

Vom Prinzip her würde das bisher so aussehn:

void myRxIsr(char newChar)
{
  if(newChar != 0x0D)  //Prüfen auf CR
  {
    myString++ = newChar;
  }
  else
  {
    myString = '\0';
  }
}

Nun zeigt aber der Pointer myString durch das hochzählen auf das 
Zeichen'\0'.
Andere Funktionen die myString lesen können also den String nicht mehr 
benutzen.

Außerdem soll der String bei Empfang einer neuen Zeichenkette 
überschrieben werden.
Hier steh ich dann völlig auf dem Schlauch.

Ich denke ich brauche dazu einen Hilfszeiger, richtig? Sonst fehlt mir 
durch das Hochzählen ja später die Startadresse des Strings.

static Variablen sind eher unerwünscht. Globale Variablen sind erlaubt 
aber sollten auch eher sparsam verwendet werden.

Wie würdet ihr das angehen?

Viele montagmorgendliche Grüße,

Lugge

von Matthias L. (Gast)


Lesenswert?

>Außerdem soll der String bei Empfang einer neuen Zeichenkette
>überschrieben werden.

Es scheint sich um RS232 zu handeln. Das erste, was du dir überlegen 
musst, ist:

"Wann beginnt ein neuer String?"

Ich mache das immer so, dass über RS232 mehrere Bytes hintereinander 
gesendet werden, diese gehören zum selben Frame und landen hinternander 
im Rx-Puffer (bei dir myString). Sobald jetzt auf der RX-Leitung eine 
Pause grösser zB 2Byte-Zeiten erkannt wird, ist der Empfang des 
aktuellen Frames beendet und die Auswertung kann beginnen...

Darüber musst du dir zuerst Gedanken machen...

von DirkB (Gast)


Lesenswert?

Nimm doch den Index als Hilfsvariable, dann kannst du auch gleich die 
Länge vergleichen.
Zudem würde ich nach jedem empfangenen Zeichen die '\0' ans Ende 
schreiben, damit der String auf jeden Fall terminiert ist.

von DirkB (Gast)


Lesenswert?

Matthias Lipinsky schrieb:
> "Wann beginnt ein neuer String?"

Wenn er vorher '\n' empfangen hat. Sein Problem ist, dass er den Zeiger 
auf den Pufferspeicher verändert und somit nicht mehr weiß, wo sein 
Speicher anfängt.

von Lugge (Gast)


Lesenswert?

Danke DirkB, des hört sich sinnvoll an!

@Matthias Lipinsky: Naja also Gedanken hab ich mir scho gemacht.
Ein String hört eben mit <CR>, <LF> oder <CR><LF> auf. Auf diese Zeichen 
wird auch geprüft (in meinem obigen Beispiel momentan nur auf <CR>.

Ich bastl mal weiter, hab jetzt wenigstens nen Ansatz!
Danke!

von Matthias L. (Gast)


Lesenswert?

>Wenn er vorher '\n' empfangen hat.

Ok. Möglich.

>Sein Problem ist, dass er den Zeiger auf den Pufferspeicher verändert und >somit 
nicht mehr weiß, wo sein Speicher anfängt.

Dann braucht er zwei Zeiger. Einen zum Empfang, und einen zum Auswerten. 
Etwa so:
1
uint8_t  au8RxBuff[20];
2
uint8_t  u8RxIdx;
3
4
uint8_t  au8RxData[20]
5
uint8_t  u8RxLen;
6
uint8_t  u8RxCnt;
7
uint8_t  u8RxCntLast;
8
9
ISR (Uart_Receive)
10
{
11
  uint8_t  u8Data = UDR;
12
13
  //-- Frame ende erkannt --------------
14
  if ( u8Data == '/n' )
15
  {
16
    au8RxBuff[u8RxIdx++] = 0;
17
    //-- Frame zur Auswertung geben 
18
    au8RxData = au8RxBuff;
19
    u8RxLen   = u8RxIdx;
20
    u8RxIdx   = 0;
21
    return;
22
  }
23
24
  //-- nein, nur speichern -------------
25
  au8RxBuff[u8RxIdx++] = u8Data ;
26
}
27
28
29
void main ( void )
30
{
31
  ...
32
33
  while ( 1 )
34
  {
35
    ...
36
    if ( u8RxCntLast != u8RxCnt )
37
    {
38
      u8RxCntLast = u8RxCnt;
39
40
      // mach was mit:
41
      au8RxData;
42
      u8RxLen;
43
    }
44
  }
45
}

von Lugge (Gast)


Lesenswert?

Also anstatt den Zeiger selber zähle ich nun einen Index rauf:

static uint8 index = 0;

und setze ihn im else-Teil, also bei Empfang eines Abschlusszeichens 
wieder auf 0.

Terminieren muss ich im else-Teil nicht mehr, das mache ich jetzt 
jedesmal wenn ein neues Zeichen reinkommt sofort. Ist wohl sicherer, in 
Bezug auf andere Funktionen die den String lesen. (Wobei ich eigentlich 
noch ein Completeflag setze, aber zuviel Sicherheit schadet ja auch 
nicht)

Hätte es zwar gerne ohne Hilfszeugs gelöst weils einfach eleganter ist, 
aber sollte so klappen. Muss jetzt erstmal ausführlich testen!

Grüße,
Lugge

von DirkB (Gast)


Lesenswert?

Lugge schrieb:
> Hätte es zwar gerne ohne Hilfszeugs gelöst weils einfach eleganter ist,

Dann musst du jedesmal, wenn du ein Zeichen empfängst, die Stringlänge 
feststellen.
Ist das eleganter?

von Matthias L. (Gast)


Lesenswert?

In meinem Post ist ein kleiner Fehler:

Vor das return in der ISR muss noch ein u8RxCnt++ dazu.

von Lugge (Gast)


Lesenswert?

Nope, auf keinen Fall! :)

von Karl H. (kbuchegg)


Lesenswert?

Matthias Lipinsky schrieb:
> In meinem Post ist ein kleiner Fehler:

Ja ...

> Vor das return in der ISR muss noch ein u8RxCnt++ dazu.

... aber der ist es nicht.


(Und die Sache mit deinem CntLast und der damit zusammenhängen Erkennung 
eines neuen Strings: keine gute Idee)

von Karl H. (kbuchegg)


Lesenswert?

> char myString[20]
>
> Die Funktion soll mir nun das empfangene Zeichen anhängen. Bei Empfang
> eines Abschlusszeichens 'CR' soll der String terminiert werden.
>
> Vom Prinzip her würde das bisher so aussehn:
>
> void myRxIsr(char newChar)
> {
>   if(newChar != 0x0D)  //Prüfen auf CR
>   {
>     myString++ = newChar;
      *myString++ = newChar;  // aber siehe Text
>  }
>  else
>  {
>    myString = '\0';
     *myString = '\0';
>  }
> }
>
> Nun zeigt aber der Pointer myString durch das hochzählen auf
> das Zeichen'\0'.

Nö. Das liegt daran, dass myString schon mal gar kein Pointer ist. Daher 
werden auch alle Operationen, bei denen du einen vermeintlichen Pointer 
'myString' manipulieren willst, nicht funktionieren.
myString ist ein Array und kein Pointer.

> Andere Funktionen die myString lesen können also den String
> nicht mehr benutzen.

Doch, natürlich können sie das. myString ist ein Array und dort steht 
vom Anfang an gerechnet immer der String drinnen.

von Matthias L. (Gast)


Lesenswert?

Möglichkeiten gibt es viele. Was wäre denn zB eine bessere Idee?

PS: du meinst wahrscheinlich das:
>> au8RxData = au8RxBuff;

Aber bei pseudocode sollte es passen.

von Lugge (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Nö. Das liegt daran, dass myString schon mal gar kein Pointer ist. Daher
> werden auch alle Operationen, bei denen du einen vermeintlichen Pointer
> 'myString' manipulieren willst, nicht funktionieren.
> myString ist ein Array und kein Pointer.

Wird bei dei Deklaration eines "höheren Datentypes" wie String, Array 
oder Struct nicht eigentlich nur ein Pointer auf das erste Element 
erzeugt (+der genügend Speicher reserviert)?

von Haudrauf ! (Gast)


Lesenswert?

Sinnvollerweise haelt man sich einen Index, der auf auf das letzte 
Element zeigt, anstelle der idiotischen Null, die man erst suchen muss.

von Lugge (Gast)


Lesenswert?

Jo so machs ich momentan. Klappt aber nochned 100%ig.

Ich habe ein char * message_ptr[20] definiert.

Kann ich dann (unter Verwendung eines Indexes) schreiben:

*message_ptr[i] = 'c';
i++;

Absicht dahinter: ich dereferenziere den Zeiger um ein Zeichen an die 
Speicherstelle zu schreiben. Da ich nicht immer nur das erste Zeichen 
verändern will setze ich zusätzlich noch den Index dahinter.

Habe so ein Konstrukt noch nie verwendet, jetzt weis ich nicht ob mein 
unerwünschtes Verhalten von der Kombination Sternoperator + Index 
kommt...

von DirkB (Gast)


Lesenswert?

Achtung! char * message_ptr[20] ist ein Feld aus 20 Zeigern auf char.

Entweder Zeiger oder Array.
Lass den * weg. In beiden Fällen.

Und i ist ein extrem blöder Variablenname für eine global Variable.
1
char myString[20];
2
int  myString_i = 0;
3
4
void myRxIsr(char newChar)
5
{ 
6
// Fehlt: Überprüfen ob myString_i zu groß ist
7
  if(newChar != 0x0D)  //Prüfen auf CR
8
  {
9
    myString[myString_i++] = newChar;
10
    myString[myString_i] = '\0';
11
  } else 
12
  { myString_i = 0;  // oder Flag setzen.
13
  }
14
}

von Lugge (Gast)


Lesenswert?

Ok, mein Fehler, nicht bedacht!

Aber würde die Zeile

*message_ptr[i] = 'c';

funktionieren? Nur aus Interesse...

Deine Variablendefinition und Initialisierung natürlich vorrausgesetzt!

von DirkB (Gast)


Lesenswert?

Ja.

Aber nur wenn bei message_ptr[i] ein Zeiger auf einen gültigen 
Speicherbereich hinterlegt wurde.

Z.B durch ein message_ptr[i] = myString; // (Definition von myString 
siehe bei dir)
Dann steht in myString das 'c'

von Lugge (Gast)


Lesenswert?

Ok ich glaub wir reden aneinander vorbei.

Mit *message_ptr[i] will ich nicht auf ein Array von Zeigern zugreifen 
sondern den Wert an der Stelle i verändern.

Ich versuchs mal mit so wenig Zeilen wie möglich auszudrücken:

Ich definiere mir einen String und einen Zeiger darauf.
Außerdem einen Index.

char message[20];
char *message_ptr;
message_ptr = &message;
uint8 i=3;

Hätten dann die beiden folgenden Zeilen den selben Effekt?

message[i] = 'c';
*message_ptr[i] = 'c';

von DirkB (Gast)


Lesenswert?

char message[20];
char *message_ptr;

message_ptr = message; // Hier brauchst du keinen Adressoperator (&) ...
uint8 i=3;

Dann haben die beiden folgenden Zeilen den selben Effekt!

message[i] = 'c';
message_ptr[i] = 'c'; // und hier keinen Dereferenzierungsoperator (*)

Das gleicht sich nur, das ist nicht das Selbe.
Ein Unterschied zwischen Array und Zeiger ist, dass message = 
message_ptr; nicht geht.

von Lugge (Gast)


Lesenswert?

Danke dir!
Es funkt! :)

Kannst du mir evtl. noch kurz erklären, was da genau passiert und was in 
meinem obigen Beispiel anders ist? Ich geb zu, 100%ig durchgestiegen bin 
ich da jetzt nicht!

Und dann noch eine kleine Erweiterung:

Sagen wir ich definiere mir einen eigenen Typen für 20 Zeichen, ich 
ändere also die Zeile:

char message[20];

um in:

typedef char myUsartMsgType[20];
myUsartMsgType message;

Der Rest bleibt.
Was genau ist da dran anders? Wieso funktioniert das nicht?

von Karl H. (kbuchegg)


Lesenswert?

Lugge schrieb:
> Kannst du mir evtl. noch kurz erklären, was da genau passiert und was in
> meinem obigen Beispiel anders ist? Ich geb zu, 100%ig durchgestiegen bin
> ich da jetzt nicht!

Der wichtigste Punkt:

Ein Array ist kein Pointer!


Ein Array legt in deinem Fall eine Speicherfläche fest in der 20 char 
untergebracht werden können. Diese Speicherfläche hat einen Namen und 
wird unter diesem Namen angesprochen

Ein Pointer hingegen ist eine eigenständige Variable, deren Inhalt die 
Adresse einer Speicherfläche ist. Der Pointer stellt nur den Speicher 
für den Verweis dar. Um die tatsächliche Speicherfläche muss man sich 
seperat kümmern. Ein Pointer 'kann' auf Speicher zeigen, muss es aber 
nicht. Und ein Pointer der gerade erst zur Welt gekommen ist, zeigt 
irgendwohin aber nirgends konkretes, wenn ich als Programmierer nicht 
dafür sorge.


Array
#####

   message
   +---+---+---+---+---+---+---+- ... -+---+---+---+
   |   |   |   |   |   |   |   |       |   |   |   |
   +---+---+---+---+---+---+---+- ... -+---+---+---+


Pointer
#######


   message
   +----+
   | o---------+
   +----+      |
               |
               v
              +---+---+---+---+---+---+---+- ... -+---+---+---+
              |   |   |   |   |   |   |   |       |   |   |   |
              +---+---+---+---+---+---+---+- ... -+---+---+---+


Das die beiden Dinge hier syntaktisch gleich aussehen, liegt an 3 Dingen 
die zusammenspielen:

* in C wird der Name eines Arrays, wenn er alleine steht (also ohne 
Indexoperation) automatisch wie die Startadresse der Speicherfläche 
angesehen.

* In C ist die Indexoperation [] in Ausdrücken von Pointerarithmetik 
definiert.

      p[i]  <==>  *(p + i)

d.h. man kann jede Array-Indexoperation in eine gleichwertige 
Pointeroperation umformen und vice versa. Syntaktisch!
Und genau das tut auch der Compiler, wenn er eine Indexoperation 
behandelt. Er wandelt sie als erstes in die gleichwertige 
Pointer-Notation.

* Der Ausdruck p + i (also: Pointer + Offset) ist in C so definiert, 
dass p auf eine Speicherfläche zeigt, und der Offset i automatisch immer 
die Größe das Basistyps des Zeigers mit eingerechnet wird. Wenn p also 
ein Int-Pointer ist, dann wird i automatisch mit sizeof(int) 
multiplizert, ist p ein Double-Pointer, so wird i mit sizeof(double) 
multipliziert usw. Hier drinn steckt die Erklärung, warum die 
Pointer-Array_Index Dualität überhaupt funktioniert.


schreibst du also

  message[5] = 'c';

dann wandelt sich der COMpiler das intern um in

  *(message + 5) = 'c'

hier steht mesage für sich alleine. message fungiert hier also als die 
Startdresse der SPeicherfläche:


   message
   +---+---+---+---+---+---+---+- ... -+---+---+---+
   |   |   |   |   |   |   |   |       |   |   |   |
   +---+---+---+---+---+---+---+- ... -+---+---+---+

    ^
    |
   Startadresse der Speicherfläche

Zu dieser Speicherfläche werden noch 5 dazugezählt, man landet also beim 
5. Kästchen von dieser Startadresse aus gesehen rechts.

   +---+---+---+---+---+---+---+- ... -+---+---+---+
   |   |   |   |   |   |   |   |       |   |   |   |
   +---+---+---+---+---+---+---+- ... -+---+---+---+
                        ^
                        |
                      also hier

und durch den * wird dann hier das Zeichen abgelegt.

   +---+---+---+---+---+---+---+- ... -+---+---+---+
   |   |   |   |   |   |'c'|   |       |   |   |   |
   +---+---+---+---+---+---+---+- ... -+---+---+---+


Schereibst du aber

  char* msg = message;

dann hast du etwas ganz anderes konstruiert.


   msg
   +---+
   | o |
   +-|-+
     |
     |
 +---+
 |   message
 |   +---+---+---+---+---+---+---+- ... -+---+---+---+
 +-> |   |   |   |   |   |   |   |       |   |   |   |
     +---+---+---+---+---+---+---+- ... -+---+---+---+

die OPeration

   *(msg + 5) = 'a';

wird ein wenig anders abgearbeitet. Der Inhalt von msg wird hergenommen, 
weil es sich hier ja um eine echte Variable handelt. Und das ist nun mal 
ein Pointer auf den Anfang der Speicherfläche (also der grafisch 
eingezeichnete Pfeil). Wieder wird dieser Pfeil temporär um 5 Kästchen 
nach rechts gerückt und dort wo dieser Pfeil dann hinzeigt, das 'c' 
reingeschrieben (durch den *)

   msg
   +---+
   | o |
   +-|-+
     |
     |
 +---+
 |   message
 |   +---+---+---+---+---+---+---+- ... -+---+---+---+
 +-> |   |   |   |   |   |   |   |       |   |   |   |
     +---+---+---+---+---+---+---+- ... -+---+---+---+
       ^   ^   ^   ^   ^   ^
       |   |   |   |   |   |
       |   |   |   |   |  5. Verschiebung
       |   |   |   |  4. Verschiebung
       |   |   |  3. Verschiebung
       |   |  2. Verschiebung
       |  1. Verschiebung
      0. Verschiebung (hier zeigt der Pointer msg hin)


Die Operation ist ähnlich aber doch ganz anders.

Und aufgrund der Tatsache, dass es einen Pointer Array DUalismus gibt

      a[i] <==> *(p + i)

kann man die Operation

   *(msg + 5) = 'a';

auch so ausdrücken

   msg[5] = 'a';


> um in:
>
> typedef char myUsartMsgType[20];
> myUsartMsgType message;
>
> Der Rest bleibt.
> Was genau ist da dran anders?

Nichts.

> Wieso funktioniert das nicht?

Weil du irgendwo anders einen Fehler gemacht hast?

von Lugge (Gast)


Lesenswert?

Danke für diese Erklärung! Perfekt! Das muss ich jetzt aber erstmal 
wirken lassen :)

//Weil du irgendwo anders einen Fehler gemacht hast?

Wahrscheinlich, ich hab den Code oben stark vereinfach gepostet, denn 
eigentlich verteilen sich die defines und Funktionen auf mehreren .h und 
.c Files. Da muss ich nochmal drüberschaun!

von Lugge (Gast)


Lesenswert?

Edit:

Und schon gefunden!

Ich definiere mir:
myUsartMsgType message;

Und übergebe message dann an eine Funktion mit dem Prototyp:

void getMessage(myUsartMsgType *message_ptr);

Ändere ich" myUsartMsgType" in "char" ab dann gehts.

Um "myUsartMsgType" zu benutzen muss ich den Stern vor message_ptr 
weglassen denn myUsartMsgType entspricht ja nicht einem char sondern 
einem char[20], was im Prinzip ja schon ein char* ist, richtig?

Ansonsten, Danke nochmal an alle!

von Karl H. (kbuchegg)


Lesenswert?

Lugge schrieb:

> weglassen denn myUsartMsgType entspricht ja nicht einem char sondern
> einem char[20], was im Prinzip ja schon ein char* ist, richtig?

An dieser Stelle: ja

Arrays werden an Funktionen immer übergeben, in dem die Startadresse des 
Arrays übergeben wird. Ergo braucht die Funktion eine Pointervariable, 
in der diese Startadresse gespeichert werden kann.

Und in Funktionsdefinitionen sind die Schreibweisen

  void foo( datentyp * argument )

und

  void foo( datentyp argument[] )

gleichwertig. Da besteht kein Unterschied. Auch wenn es zunächst nicht 
so aussieht, ist auch im 2.ten Fall die Variable "argument" eine 
Pointervariable.

In deinem Fall steckt die Arraysyntax durch den typedef im Datentyp 
drinnen. Ändert aber nichts daran, dass in der Funktionsdefinition in 
der Argumentliste ein Array steckt, welches an dieser Stelle automatisch 
in einer Pointervariablen ausgeführt wird.

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.