Forum: Mikrocontroller und Digitale Elektronik Komisches Verhalten von Pointer auf Struct


von Daniel K. (daniel_k62)


Lesenswert?

Hallo!

Ich möchte verschiedene Befehle an ein GSM Modul schicken. Dafür habe 
ich ein Struct definiert, in dem alle Strings (Befehle) stehen.

Dies sieht so aus:
1
#define F_CPU 11059200UL
2
#define BAUD 115200UL
3
#define BAUD_VAL ((F_CPU + BAUD * 8) / (BAUD * 16) - 1)
4
5
#include <avr/io.h>
6
#include <util/delay.h>
7
#include <USART.c>
8
#include <LCD.c>
9
10
// define struct with all AT commands
11
typedef struct
12
{
13
  unsigned char AT[3];
14
  unsigned char ATE0[5];
15
  unsigned char AT_CPIN[13];
16
  unsigned char AT_COPS[10];
17
  unsigned char AT_CMGF[10];
18
  unsigned char AT_CNMI[18];
19
  unsigned char AT_CMGR[10];
20
  unsigned char AT_CMGS[24];
21
  unsigned char SMS_TEXT_ON[9];
22
  unsigned char SMS_TEXT_OFF[9];
23
} CHAR_ARRAY;
24
25
// initialise struct and define pointer
26
CHAR_ARRAY AT_CMD =
27
{
28
  "AT\0",
29
  "ATE0\0",
30
  "AT+CPIN=4053\0",
31
  "AT+COPS=?\0",
32
  "AT+CMGF=1\0",
33
  "AT+CNMI=1,1,0,2,1\0",
34
  "AT+CMGR=0\0",
35
  "AT+CMGS=\"+436641111111\"\0",
36
  "HZG EIN!\0",
37
  "HZG AUS!\0"
38
};
39
40
// create pointer to command struct
41
CHAR_ARRAY *AT_CMD_PTR = &AT_CMD;

Die Subroutine, die die Stings dann über USART an das GSM Modul schickt 
siehst so aus:
1
void GSM_WRITE (unsigned char *CMD)
2
{
3
  // clear display
4
  LCD_COMMAND(0x01);
5
6
  while (*CMD)
7
  {
8
    LCD_DATA(*CMD);
9
    USART_TX(*CMD++);
10
  }
11
}

Zum schluss, hier noch der Aufruf über eine Art state-machine. Der 
Subroutine wird der Pointer auf den jeweiligen string im struct 
übergeben.
1
while (1)
2
  {
3
    switch (STATE)
4
    {
5
      // init GSM module, just wait and send some CR
6
      case 0:
7
      _delay_ms(1000);
8
      _delay_ms(1000);
9
      _delay_ms(1000);
10
11
      USART_TX(0x0D);
12
      USART_TX(0x0D);
13
      USART_TX(0x0D);
14
15
      NEXT_STATE = 1;
16
      break;
17
18
      // send AT command to check state
19
      case 1:
20
      GSM_WRITE(AT_CMD_PTR -> AT);
21
22
      ERROR = GSM_CHECK();
23
      if (ERROR == 0) NEXT_STATE = 2;
24
      if (ERROR == 1) NEXT_STATE = 1;
25
      break;
26
27
      // turn off echo
28
      case 2:
29
      GSM_WRITE(AT_CMD_PTR -> ATE0);
30
31
      ERROR = GSM_CHECK();
32
      if (ERROR == 0) NEXT_STATE = 3;
33
      if (ERROR == 1) NEXT_STATE = 2;
34
      break;
35
36
      // send PIN code
37
      case 3:
38
      GSM_WRITE(AT_CMD_PTR -> AT_CPIN);
39
40
      ERROR = GSM_CHECK();
41
      if (ERROR == 0) NEXT_STATE = 4;
42
      if (ERROR == 1) NEXT_STATE = 3;
43
      break;
44
45
      // set SMS to text mode (1)
46
      case 4:
47
      GSM_WRITE(AT_CMD_PTR -> AT_CMGF);
48
49
      ERROR = GSM_CHECK();
50
      if (ERROR == 0) NEXT_STATE = 5;
51
      if (ERROR == 1) NEXT_STATE = 4;
52
      break;
53
54
      // set SMS notification and storage (1,1,0,2,1)
55
      case 5:
56
      GSM_WRITE(AT_CMD_PTR -> AT_CNMI);
57
58
      ERROR = GSM_CHECK();
59
      if (ERROR == 0) NEXT_STATE = 6;
60
      if (ERROR == 1) NEXT_STATE = 5;
61
      break;
62
63
      // idle state, wait for input from GSM (SMS)
64
      case 6:
65
      while(1) BLINK();
66
    }
67
68
    // set current state to next state
69
    STATE = NEXT_STATE;
70
  }

Es gibt noch viel mehr code mit den ganzen LCD subroutinen, aber ich 
glaube das hat mit meinem probmel nichts zum tun. Wenn ich den code so 
compiliere und an den Attiny2313 schicke, dann kommt ein ganz komisches 
verhalten raus. Die Pointeradressen, die der subroutine übergeben 
werden, stimmen nicht, die Ausgabe an der rs232 schnittstelle sieht dann 
so aus:

Statt dem CPIN string: #Q
Statt dem CNMI string: <>i
Statt dem CMGF string: 11AT+CNMI=1

und so weiter. Es werden also irgendwelche chars übertragen. teilweise 
dann auch wieder halbe strings. So, jetzt der komische / verwirrende 
Teil. wenn ich vor dem struct, ein dummy array mit mindestens 2 x 10 
elementen definiere (trial and error gg), funktioniert alles 
einwandfrei:
1
unsigned char DUMMY[2][10] = {"X","X"};
2
3
// initialise struct and define pointer
4
CHAR_ARRAY AT_CMD =
5
{
6
  "AT\0",
7
  "ATE0\0",
8
  "AT+CPIN=4053\0",
9
  "AT+COPS=?\0",
10
  "AT+CMGF=1\0",
11
  "AT+CNMI=1,1,0,2,1\0",
12
  "AT+CMGR=0\0",
13
  "AT+CMGS=\"+436645416393\"\0",
14
  "HZG EIN!\0",
15
  "HZG AUS!\0"
16
};

Womit kann das zusammenhängen? wird das struct vielleicht in 
irgendwelchen nicht zulässigen speicherbereichen definiert? Ich hoffe 
die Frage ist so halbwegs verständlich :)

Vielen Dank im Voraus!

Daniel

von Peter D. (pdiener) Benutzerseite


Lesenswert?

Der Nullabschluss ist in "AT" bereits enthalten.
Im Fall von "AT\0" werden 4 Byte verbraucht.

Grüße,

Peter

von Klaus W. (mfgkw)


Lesenswert?

Ja, und noch dazu:
Evtl. (insbesondere, wenn die Strings alle konstant sind), ist es auch 
viel einfacher, gar nicht sie in der struct zu halten, sondern dort nur 
Zeiger abzulegen (const char*).
Dann brauchst du gar nicht zu zählen, wie lang sie sind; der Compiler 
macht den Rest.

von Daniel K. (daniel_k62)


Lesenswert?

Guten Morgen!

Danke für die schnellen Antworten! Also wenn ich das Array "AT" im 
Struct definiere, hat das die größe 3 obwohl nur 2 chars enthalten sind? 
Das '\0' Zeichen wird also automatisch hinzugefügt?

Erklährt diese falsche größendefinition auch das komische verhalten bei 
der ausgabe der strings?

@Klaus: Hast du für für die abgelegten Zeiger vielleicht ein kurzes 
code-schnipsel als beispiel? Ich bin leider noch nicht so fit was 
pointer betrifft  :)

Vielen Dank!

von Peter (Gast)


Lesenswert?

Ja, wenn Du ein String definierst, dann fügt der Compiler automatisch 
noch das Stringende \0 hinzu. das erklärt dann auch das komische 
Verhalten, die Struktur-Pointer passen nicht mehr...

von Stefan E. (sternst)


Lesenswert?

Daniel K. schrieb:
> Erklährt diese falsche größendefinition auch das komische verhalten bei
> der ausgabe der strings?

Nein, das ist nicht die Ursache. Und die Größendefinition ist auch nicht 
falsch. Bei
1
char a[3] = "AT\0";
wird die automatische Null weggeschnitten und das Endergebnis ist exakt 
das gleiche wie beim "korrekteren"
1
char a[3] = "AT";
oder
1
char a[] = "AT";

Daniel K. schrieb:
> wenn ich vor dem struct, ein dummy array mit mindestens 2 x 10
> elementen definiere (trial and error gg), funktioniert alles
> einwandfrei:

Dann hast du vermutlich irgendeine Art von Memory-Corruption, z.B. ein 
Out-Of-Bounds Schreibzugriff auf ein Array.

von Karl H. (kbuchegg)


Lesenswert?

Daniel K. schrieb:

> @Klaus: Hast du für für die abgelegten Zeiger vielleicht ein kurzes
> code-schnipsel als beispiel? Ich bin leider noch nicht so fit was
> pointer betrifft  :)

Dann solltest du um die Dinger einen Bogen machen.
Bis jetzt konnte ich deinem Beispiel nicht wirklich entnehmen, wozu du 
da überhaupt eine struct brauchst und warum du auf die struct über einen 
Pointer zugreifst.

Alternativ kannst und solltest du natürlich die Dinge allerdings auch 
richtig lernen. Mit STrings, und damit auch mit Pointern, korrekt 
hantieren zu können ist eine der Grundfertigkeiten in C

So würde das aussehen
1
// define struct with all AT commands
2
typedef struct
3
{
4
  char* AT;
5
  char* ATE0;
6
  char* AT_CPIN;
7
  char* AT_COPS;
8
  char* AT_CMGF;
9
  char* AT_CNMI;
10
  char* AT_CMGR;
11
  char* AT_CMGS;
12
  char* SMS_TEXT_ON;
13
  char* SMS_TEXT_OFF;
14
} CHAR_ARRAY;
15
16
// initialise struct and define pointer
17
CHAR_ARRAY AT_CMD =
18
{
19
  "AT",
20
  "ATE0",
21
  "AT+CPIN=4053",
22
  "AT+COPS=?",
23
  "AT+CMGF=1",
24
  "AT+CNMI=1,1,0,2,1",
25
  "AT+CMGR=0",
26
  "AT+CMGS=\"+436641111111\"",
27
  "HZG EIN!",
28
  "HZG AUS!"
29
};

Das würde dem ganze dann wenigstens die Wendung geben, die eine 
derartige Struktur rechtfertigen würde: Für unterschiedliche Geräte 
sehen die Strings anders aus und haben natürlich auch andere Längen, was 
mit dieser Lösung kein Problem mehr darstellt.

Aber wie Stefan schon sagte: Das ist nicht dein eigentliches Problem. Da 
muss in deinem Programm noch was anderes faul sein.


PS: Deine konsequente Grossschreibung aller Variablennamen ist keine 
gute Idee. Wenn es eine Übereinkunft gibt, an die sich tatsächlich mehr 
oder weniger alle C-Programmierer weltweit halten, dann ist es die, 
dass Namen komplett in Grossbuchstaben ausschliesslich für Makros 
reserviert sind. Im Code kann es einen Unterschied machen, ob man es mit 
einem echten Makro oder mit einer echten Funktion zu tun hat, so dass es 
an manchen Stellen lebenswichtig sein kann, diese Information schnell zu 
sehen. Die Konvention von "ausschliesslich Grossbuchstaben sind ein 
Kennzeichen für ein Makro" erfüllt diese Information, so dass es Sinn 
macht sich an diese Konvention zu halten.
Auch du solltest das tun. Denn auf lange Sicht gesehen erzeugst du mit 
einer anderen Konvention (deiner eigenen) nur Konfusion.

von Klaus W. (mfgkw)


Lesenswert?

Karl Heinz Buchegger schrieb:
> So würde das aussehen

danke, ja.
So meinte ich das.

Karl Heinz Buchegger schrieb:
> Dann solltest du um die Dinger einen Bogen machen.

Wenn er Felder nimmt, hat er doch auch Zeiger - er sieht nur erst etwas 
später.

von Karl H. (kbuchegg)


Lesenswert?

Klaus Wachtler schrieb:

>> Dann solltest du um die Dinger einen Bogen machen.
>
> Wenn er Felder nimmt, hat er doch auch Zeiger - er sieht nur erst etwas
> später.

Jein.
Ein Array wird per Zeiger an eine Funktion übergeben. Schon richtig. 
Aber ein Array ist kein Zeiger.

von Stefan (Gast)


Lesenswert?

Im Programm ist die Verwendung der Struktur faul. Eine Struktur ist kein 
Array und somit zieht sowas wie *CMD++ nicht. Damit würde dein Programm 
auf speicher zurück greifen der hinter deine Struktur liegt. Also leg 
dir lieber ein Array mit Pointern auf const char an dann kannst du 
solche späße wie CMD++ machen.

Gruß Stefan

von Stefan E. (sternst)


Lesenswert?

Stefan schrieb:
> Im Programm ist die Verwendung der Struktur faul. Eine Struktur ist kein
> Array und somit zieht sowas wie *CMD++ nicht.

Sorry, aber das ist Unsinn. CMD ist ja kein Pointer auf die Struktur, 
sondern auf ein Array in dieser. Mit der Funktion GSM_WRITE und der 
Verwendung von *CMD++ darin ist alles in Ordnung.

von Daniel K. (daniel_k62)


Lesenswert?

Hallo Leute!

wow, sehr viel input, danke euch. Das ist mein erstes Atmel C Programm, 
habe früher immer PICs mit ASM programmiert, deshalb noch die ganzen 
"Unschönheiten" im code.

Ist es theoretisch nicht egal ob man einzelne Arrays anlegt, die die 
ganzen Strings enthalten oder die alle zusammen in einem struct 
unterbringt? Der Hintergrund für das struct war eigentlich der ein 
Artikel über Codeoptimierung. Die Idee des Zusammefassens von Variablen 
in einem Struct hat mir gut gefallen:

http://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Statische_.28globale.29_Variablen_in_einer_Struktur_sammeln

Zu den Variablennamen: Danke für den tip. wollte hier natürlich nicht 
für verwirrung sorgen. Wird auf jeden Fall beim nächsten Projekt 
berücksichtigt.

Ich werde heute Abend das Programm mal auf einem anderen Attiny laufen 
lassen um zu sehen, ob das unerklärbare Verhalten immer noch auftritt.

Daniel

von Karl H. (kbuchegg)


Lesenswert?

Daniel K. schrieb:

> Ist es theoretisch nicht egal ob man einzelne Arrays anlegt, die die
> ganzen Strings enthalten oder die alle zusammen in einem struct
> unterbringt?

Mit der jetzigen struct Lösung (die mit den Pointern) verbrauchst du 
mehr Speicher. Die vorhergehende struct Lösung hatte das Problem, dass 
ein Ändern der Strings viel Arbeit nach sich zieht.

> Der Hintergrund für das struct war eigentlich der ein
> Artikel über Codeoptimierung. Die Idee des Zusammefassens von Variablen
> in einem Struct hat mir gut gefallen:
>
> 
http://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Statische_.28globale.29_Variablen_in_einer_Struktur_sammeln
>

Vergiss den Artikel. Besser gesagt diesen einen Abschnitt über 
Strukturvariablen. Das dort gezeigte Beispiel ist nicht repräsentativ, 
weil dieses gewählte Beispiel etwas ausser acht lässt: Die 3 Variablen 
sind nicht voneinander unabhängig, sondern haben als Uhrzeit einen 
logischen Zusammenhalt. Den hast du mit deinen Strings aber nicht.

von Peter D. (pdiener) Benutzerseite


Lesenswert?

Alleine die ganzen Strings brauchen bei einem ATTiny2313 (oder welcher 
Tiny ist da drin?) über 80% vom Ram. Nachdem die gezeigten Sourcen nicht 
komplett sind, ist es durchaus möglich, dass es noch wesentlich mehr 
ist. Somit tippe ich auf einen Stackoverflow.

Dass der gcc bis zu eine \0 am Stringende automatisch wegschneidet hab 
ich noch nicht gewusst. Stimmt aber, habs gerade ausprobiert. Erst wenn 
mehrere \0 am Ende sind, sind die auch im Code enthalten.

Die ganzen Texte sollten im Flash abgelegt werden und die Fehler mit den 
Pointern behoben werden (es gibt beim Compilieren von obigem Code 
mindestens 2 Warnings wegen falschen Datentypen).

Grüße,

Peter

von Klaus W. (mfgkw)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Jein.
> Ein Array wird per Zeiger an eine Funktion übergeben. Schon richtig.
> Aber ein Array ist kein Zeiger.

Auch in anderen Ausdrücken ist der Feldname ein (const-) Zeiger auf das 
erste Element.

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.