Forum: Mikrocontroller und Digitale Elektronik Pointer in STRUCT wollen einfach nicht funktionieren! (AVR-GCC)


von Patrick Z. (zorpat)


Lesenswert?

Hallo Leute,
ich werd noch blöde.. :)

Ich versuche gerade einen kleinen Modbus Slave aufzubauen und kämpfe 
gerade mit Pointern die auf structs mit weiteren Pointern ziele 
sollten..

Klingt kompliziert, ist es aber nur wenn man den Wald vor lauter Bäumen 
nicht mehr sieht.. :P

Die Deklaration meines Structs:
1
typedef struct {
2
  uint8_t slave_address;
3
  uint8_t function_code;
4
  uint16_t *data;
5
  uint16_t crc;
6
} modbus_frame_t;

Nun würde ich gerne dieses Struct in meiner Empfangsroutine mit Daten 
füllen..
Dafür teste ich gerade einen Code wie den folgenden.. Der Compiler 
meldet (natürlich) keinen Fehler da der Code an sich ja "richtig" ist, 
aber nicht das gewünschte Ergebnis bringt da ich die Pointer usw. 
sicherlich falsch benutze..
1
void Modbus_RX_handler(void)
2
{
3
   modbus_frame_t *modbus_frame;
4
5
   modbus_frame = malloc(sizeof(*modbus_frame));
6
   modbus_frame->data = malloc(sizeof(*modbus_frame->data) * 3);
7
   modbus_frame->slave_address = RS485_getc();
8
   modbus_frame->function_code = RS485_getc();
9
10
   *modbus_frame->data = RS485_getc();
11
   *modbus_frame->data++ = RS485_getc();
12
13
   free(modbus_frame->data);
14
   free(modbus_frame);
15
}

Zum Testen versuche ich mit folgendem Code ein "Echo" an den Absender zu 
versenden..
1
   RS485_putc(*modbus_frame->data);
2
   RS485_putc(*modbus_frame->data++);

Die 1. Zeile funktioniert, aber die 2. nicht!

Wie mache ich das Richtig?
Ich komm einfach nicht drauf..

Bitte helft mir! :)

Vielen Dank!

: Bearbeitet durch User
von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Das Problem liegt nicht an der Struktur, sondern an Deiner Verwendung 
der Pointer.

> *modbus_frame->data = RS485_getc();

Und worauf zeigt "modbus_frame->data"?

Wann hast Du diesen Pointer initialisiert?


Auch wenn Du ihn initialisierst (also irgendwo Speicher zur Verfügung 
stellst und den Pointer auf dessen Anfang zeigen lässt), geht Dein 
nächster Zugriff in die Hose.

> *modbus_frame->data++ = RS485_getc();

Und wie kommst Du danach an die Adresse, auf die modubus_frame->data 
vorher zeigte?

So jedenfalls nicht.

> RS485_putc(*modbus_frame->data);
> RS485_putc(*modbus_frame->data++);

++ ist der Inkrement-Operator. Du willst aber hier modbus_frame->data 
nicht erhöhen, jedenfalls nicht dauerhaft. Denn dann weißt Du nicht 
mehr, wo der Speicherbereich anfängt (oder aufhört), auf den dieser 
Pointer eigentlich zeigen sollte.

von Rene H. (Gast)


Lesenswert?

De Zordo P. schrieb:
> modbus_frame = malloc(sizeof(*modbus_frame));

Das sollte wohl eher:
1
modbus_frame = malloc(sizeof(modbus_frame_t));

sein.

De Zordo P. schrieb:
> modbus_frame->data = malloc(sizeof(*modbus_frame->data) * 3);

???
Das kann auch nicht sein. Der Inhalt von modbus_frame->data ist erstens 
unbestimmt und zweitens macht die ganze Zeile null Sinn.

Wieviel willst Du denn für data allokieren? Das ist aus diesen Zeilen 
wirklich nicht erkennbar ausser das es drei mal soviel sein sollte ;-).

Grüsse,
René

von Daniel A. (daniel-a)


Lesenswert?

modbus_frame ist global, und du reservierst dafür dynamisch speicher, am 
anfang einer funktion, befüllst es dann mit werten, nur um am ende der 
funktion die daten ohne sie zu nutzen freizugeben?!?

Du speicherst 2 werte in einem array aus drei uint16_t werten?

Was willst du erreichen???

von Daniel A. (daniel-a)


Lesenswert?

Rene H. schrieb:
>> malloc(sizeof(*modbus_frame->data) * 3);
>
> ???
> Das kann auch nicht sein.

Das ist korrekt. Es findet keine dereferenzierung statt, die grösse 
eines uint16_t (type von *modbus_frame->data) muss zur kompiletime von 
compiler ermittelt werden.

: Bearbeitet durch User
von Patrick Z. (zorpat)


Lesenswert?

Daniel A. schrieb:
> modbus_frame ist global, und du reservierst dafür dynamisch speicher, am
> anfang einer funktion, befüllst es dann mit werten, nur um am ende der
> funktion die daten ohne sie zu nutzen freizugeben?!?

Für den Moment ja, ich habe den gesamten Code "verworfen" welcher im 
"Modbus_RX_handler(void)" das Protokoll auswertet. Ich wollte mich 
Schritt für Schritt dem Problem nähern.

Die Ausgabe:
1
   RS485_putc(*modbus_frame->data);
2
   RS485_putc(*modbus_frame->data++);
wird aktuell auch noch im "Modbus_RX_handler(void)" ausgeführt.
Also so in etwa:
1
void Modbus_RX_handler(void)
2
{
3
   modbus_frame = malloc(sizeof(*modbus_frame));
4
   modbus_frame->data = malloc(sizeof(*modbus_frame->data) * 3);
5
6
   modbus_frame->slave_address = RS485_getc();
7
   modbus_frame->function_code = RS485_getc();
8
   *modbus_frame->data = RS485_getc();
9
   *modbus_frame->data++ = RS485_getc();
10
11
   RS485_putc(*modbus_frame->data);
12
   RS485_putc(*modbus_frame->data++);
13
14
   free(modbus_frame->data);
15
   free(modbus_frame);
16
}
Kannst du mir "quick and dirty" sagen wie ich das Problem lösen kann?
Ich versuche dann die Pointer besser zu verstehen..

von Patrick Z. (zorpat)


Lesenswert?

Rene H. schrieb:
> De Zordo P. schrieb:
>> modbus_frame->data = malloc(sizeof(*modbus_frame->data) * 3);
>
> ???
> Das kann auch nicht sein. Der Inhalt von modbus_frame->data ist erstens
> unbestimmt und zweitens macht die ganze Zeile null Sinn.

Ich wollte damit die eine "data"-Variable innerhalb des Structs mit der 
Größe von 3 x uint16_t reservieren/allokieren.
Insgesamt lese ich also 2+2*3+2=10 Bytes in meinen Struct.

Danach würde ich (für den Moment) folgeden Werte in die Variablen 
einlesen wollen:
1
   slave_address = 0x01
2
   function_code = 0x02
3
   data = 0x0003
4
   data+1 = 0x0004
5
   data+2 = 0x0005
6
   crc16 = 0xFFFF
und natürlich auch wieder darauf zugreifen wollen.. :)

von Patrick Z. (zorpat)


Lesenswert?

Rene H. schrieb:
> De Zordo P. schrieb:
>> modbus_frame = malloc(sizeof(*modbus_frame));
>
> Das sollte wohl eher:
> modbus_frame = malloc(sizeof(modbus_frame_t));
>
> sein.

Sollte lt. meiner Recherche aber funktionieren..? :/
Ja?

von Patrick Z. (zorpat)


Lesenswert?

Rufus Τ. Firefly schrieb:
>> *modbus_frame->data++ = RS485_getc();
>
> Und wie kommst Du danach an die Adresse, auf die modubus_frame->data
> vorher zeigte?
>
> So jedenfalls nicht.
>
>> RS485_putc(*modbus_frame->data);
>> RS485_putc(*modbus_frame->data++);
>
> ++ ist der Inkrement-Operator. Du willst aber hier modbus_frame->data
> nicht erhöhen, jedenfalls nicht dauerhaft. Denn dann weißt Du nicht
> mehr, wo der Speicherbereich anfängt (oder aufhört), auf den dieser
> Pointer eigentlich zeigen sollte.

Ist das in meinem Beispiel nicht egal?
Bei jedem Austritt von "Modbus_RX_handler(void)" wird der Speicher eh 
wieder freigegeben..? Oder stehe ich wieder aufm Schlauch.. ;)

von Daniel A. (daniel-a)


Lesenswert?

De Zordo P. schrieb:
> Kannst du mir "quick and dirty" sagen wie ich das Problem lösen kann?

Ich bevorzuge saubere lösungen ;)
Zuerst würde ich die dynamische Speicherreservierung entfernen.
Ist der Rückgabewert von RS485_getc() ein uint8_t oder char?
1
static const int MAX_DATAS = 3;
2
3
typedef struct {
4
  uint8_t slave_address;
5
  uint8_t function_code;
6
  uint16_t data[MAX_DATAS];
7
  uint16_t crc;
8
} modbus_frame_t;
9
10
void Modbus_RX_handler(void){
11
   modbus_frame_t modbus_frame;
12
13
   modbus_frame.crc=0xffff;
14
15
   modbus_frame.slave_address = RS485_getc();
16
   modbus_frame.function_code = RS485_getc();
17
18
   modbus_frame.data[0] = (
19
    (((unsigned char)RS485_getc())<<8) |
20
     ((unsigned char)RS485_getc())
21
   );
22
   modbus_frame.data[1] = (
23
    (((unsigned char)RS485_getc())<<8) |
24
     ((unsigned char)RS485_getc())
25
   );
26
   modbus_frame.data[2] = (
27
    (((unsigned char)RS485_getc())<<8) |
28
     ((unsigned char)RS485_getc())
29
   );
30
31
   RS485_putc(modbus_frame->data[0]);
32
   RS485_putc(modbus_frame->data[0]>>8);
33
   RS485_putc(modbus_frame->data[1]);
34
   RS485_putc(modbus_frame->data[1]>>8);
35
   RS485_putc(modbus_frame->data[3]);
36
   RS485_putc(modbus_frame->data[3]>>8);
37
}

Ich nehme jetzt an, dass RS485_getc ein char zurückgibt
RS485_putc und RS485_getc funktionieren,
RS485_getc die richtigen werte empfängt,
Die bytes in networkbyteorder (big endian) ankommen,
RS485_getc auf ankommende daten wartet.

Ziemlich viele annahmen...

: Bearbeitet durch User
von Panda (Gast)


Lesenswert?

Musst du wirklich dynamisch allocieren?

Eine Variable auf dem Stack ist nach verlassen der Funktion auch wieder 
weg.
1
void Modbus_RX_handler(void)
2
{
3
  modbus_frame_t modbus_frame;
4
  modbus_frame_t* ptr_modbus_frame = &modbus_frame;
5
  uint16_t dataarray[3];
6
7
   modbus_frame.data = dataarray;
8
   modbus_frame.slave_address = RS485_getc();
9
   modbus_frame.function_code = RS485_getc();
10
   modbus_frame.data[0] = RS485_getc();
11
   modbus_frame.data[1] = RS485_getc();
12
   
13
   RS485_putc(((uint16_t*)&ptr_modbus_frame->data)[0]);
14
   RS485_putc(((uint16_t*)&ptr_modbus_frame->data)[1]);
15
}

von Patrick Z. (zorpat)


Lesenswert?

Deine Annahmen sind gut.. ;)
Ich arbeite grundsätzlich mit "saueberen" Variablen, keine "chars" oder 
"int" welche jeder Compiler etwas anders interpretiert.. :P
1
   RS485_putc(uint8_t c);
2
   RS485_getc(void); -> gibt einen "uint8_t" zurück

Zur dynamischen Alloziierung..
1. Ich muss den Speicher leider sehr sehr effizient nutzen.. da ich nur 
einen "kleinen" ATTINY2313 benutzen kann (das Board ist vorgegeben) und 
ich soll damit einen Modbus Slave bauen welcher einige der 
Modbus-Befehle können muss. Für alle ist er sowiso zu klein..

2. kenne ich die gesamte übertragene Datenpaket erst nachdem ich das 1. 
"data", sprich das 3. und 4. Byte gelesen und ausgewerten habe. In 
diesen beiden Bytes steht die weitere Größe des Pakets, welches auch 30 
Bytes groß sein könnte.

Deshalb gehe ich davon aus, dass ich nur mit der dynamischen 
Alloziierung weiterkommen kann. Mir geht sonst das RAM schon beim 
Kompilieren aus! :)

von Daniel A. (daniel-a)


Lesenswert?

De Zordo P. schrieb:
> Ist das in meinem Beispiel nicht egal?
> Bei jedem Austritt von "Modbus_RX_handler(void)" wird der Speicher eh
> wieder freigegeben..? Oder stehe ich wieder aufm Schlauch.. ;)

Da war noch ein Fehler. Er hatte dieses Schema:
1
  char data = malloc(1);
2
  *data++; // Hier wird der pointer inkrementiert, nicht die daten, auf welcher dieser zeigt
3
  free(data); // falsche adresse

von Patrick Z. (zorpat)


Lesenswert?

Daniel A. schrieb:
> De Zordo P. schrieb:
>> Ist das in meinem Beispiel nicht egal?
>> Bei jedem Austritt von "Modbus_RX_handler(void)" wird der Speicher eh
>> wieder freigegeben..? Oder stehe ich wieder aufm Schlauch.. ;)
>
> Da war noch ein Fehler. Er hatte dieses Schema:
>
1
>   char data = malloc(1);
2
>   *data++; // Hier wird der pointer inkrementiert, nicht die daten, auf 
3
> welcher dieser zeigt
4
>   free(data); // falsche adresse
5
>

Ein Fehler weniger.. ;)
1
   *modbus_frame->data--;
2
   free(modbus_frame->data);
Danke!

: Bearbeitet durch User
von Daniel A. (daniel-a)


Lesenswert?

De Zordo P. schrieb:
> Deshalb gehe ich davon aus, dass ich nur mit der dynamischen
> Alloziierung weiterkommen kann. Mir geht sonst das RAM schon beim
> Kompilieren aus! :)

Durch dynamischen Alloziierung wird der RAM auch nicht grösser.
Dafür bekommt man Memory Fragmentation. Können mehrere Datenpakete 
gleichzeitig empfangen werden? Müssen die Daten gespeichert werden?
Werden die Daten auf dem uc ausgewertet? Dann kann dafür eine State 
Maschine verwendet werden, und das Speichern der Daten wird 
möglicherweise überflüssig.
Oder werden die Daten weitergesendet?
Dann könnte man einen Ring Buffer verwenden und die Daten Häppchenweise 
weitersenden.

von Daniel A. (daniel-a)


Lesenswert?

De Zordo P. schrieb:
> Ein Fehler weniger.. ;)
>    *modbus_frame->data--;
>    free(modbus_frame->data);

Das ist auch gefährlich, das sollte man besser so lösen:
1
  uint16_t current_value = modbus_frame->data;
2
  *current_value++ = 1;
3
  *current_value++ = 2;
4
  free(modbus_frame->data);

von Patrick Z. (zorpat)


Lesenswert?

Daniel A. schrieb:
> De Zordo P. schrieb:
>> Deshalb gehe ich davon aus, dass ich nur mit der dynamischen
>> Alloziierung weiterkommen kann. Mir geht sonst das RAM schon beim
>> Kompilieren aus! :)
>
> Durch dynamischen Alloziierung wird der RAM auch nicht grösser.
> Dafür bekommt man Memory Fragmentation. Können mehrere Datenpakete
> gleichzeitig empfangen werden? Müssen die Daten gespeichert werden?
> Werden die Daten auf dem uc ausgewertet? Dann kann dafür eine State
> Maschine verwendet werden, und das Speichern der Daten wird
> möglicherweise überflüssig.
> Oder werden die Daten weitergesendet?
> Dann könnte man einen Ring Buffer verwenden und die Daten Häppchenweise
> weitersenden.

Die Fragmentierung sollte nicht eintreten, da ich eben bei allen void's 
(momentan nur 1 zum Empfang) den Speicher am Anfang reserviere und am 
Ende wieder freigebe.
Aktiv werden wohl höchstens 2 Reservierungen meines Structs aktiv sein.

Die Daten werden nicht gespeichert oder an andere Routinen 
weitergegeben, sie werden im "Modbus_RX_handler(void)" 
verarbeitet/ausgewertet und ein ACK über den RS485 Bus zurückgesandt => 
danach DESTROY.

Ein Ringbuffer exiertiert bereits im "ISR(USART_RX_vect)". Im Interrupt 
möchte ich aber keine externen Aufrufe machen.

:)

von Rene H. (Gast)


Lesenswert?

uint16_t *current_value = ...

Hast das * vergesssen, sonst gibts nichts zu dereferenzieren.

von Patrick Z. (zorpat)


Lesenswert?

Daniel A. schrieb:
> De Zordo P. schrieb:
>> Ein Fehler weniger.. ;)
>>    *modbus_frame->data--;
>>    free(modbus_frame->data);
>
> Das ist auch gefährlich, das sollte man besser so lösen:
>
1
>   uint16_t current_value = modbus_frame->data;
2
>   *current_value++ = 1;
3
>   *current_value++ = 2;
4
>   free(modbus_frame->data);
5
>

Ja dann habe ich mein Problem doch wieder oder?
Ich muss eine "doppelte" Speicherreservierung machen, 1. im Struct und 
danach 2. im "Modbus_RX_handler(void)".
Das würde bedeuten bei 10 Byte großen "data" effektiv 20 Bytes zu 
verbraten..
Die habe ich einfach nicht beim Tiny.. :(

von Patrick Z. (zorpat)


Lesenswert?

Rene H. schrieb:
> uint16_t *current_value = ...
>
> Hast das * vergesssen, sonst gibts nichts zu dereferenzieren.

==> klingt besser, jap!

von Stefan R. (srand)


Lesenswert?

Daniel A. schrieb:
> Durch dynamischen Alloziierung wird der RAM auch nicht grösser.
> Dafür bekommt man Memory Fragmentation.

Vor allem belegen die Verwaltungsinformationen selbst auch Platz.

Bei Stackvariablen wird nur der Stackpointer 
inkrementiert/dekrementiert.

von Patrick Z. (zorpat)


Lesenswert?

Stefan Rand schrieb:
> Daniel A. schrieb:
>> Durch dynamischen Alloziierung wird der RAM auch nicht grösser.
>> Dafür bekommt man Memory Fragmentation.
>
> Vor allem belegen die Verwaltungsinformationen selbst auch Platz.
>
> Bei Stackvariablen wird nur der Stackpointer
> inkrementiert/dekrementiert.

OIOIOI... ÄÄÄÄm gibts'n Beispiel dafür? :)
Ich meine ein komplettes, damit ich Zeile für Zeile verstehen kann wie 
die Abfolge genau funktioniert... :/ Bitte?? :O

von Daniel A. (daniel-a)


Lesenswert?

Wenn ein RingBuffer existiert, und die Daten nur ausgewertet werden 
sollen, würde ich die Daten ganz anders auswerten. Zuerst eine schleife 
der form:
1
while(datasAvaiable)
2
  (*parse)(RS485_getc());

Danach eine Statemachine zum parsen der daten:
1
void parse_slave_address(const char);
2
3
void(*parse)(const char) = &parse_slave_address;
4
5
typedef union {
6
  uint8_t u8[4];
7
  uint16_t u16[2];
8
  uint32_t u32;
9
  int8_t i8[4];
10
  int16_t i16[2];
11
  int32_t i32;
12
} temporary_t;
13
14
temporary_t tempdatas;
15
16
typedef struct {
17
  uint8_t slave_address;
18
  uint16_t crc;
19
} required_t;
20
21
required_t required;
22
23
enum function_codes_t {
24
  FUNCTION_A = 0,
25
  FUNCTION_B = 1,
26
};
27
28
void parse_function_code(const char c){
29
  switch((enum function_codes_t)c){
30
    case FUNCTION_A:
31
      parse = &parse_func_a_blabla;
32
    break;
33
    case FUNCTION_B:
34
      parse = &parse_func_b_blabla;
35
    break;
36
  }
37
}
38
39
void parse_slave_address(const char c){
40
  required.slave_address = c;
41
  parse = parse_function_code;
42
}

von Daniel A. (daniel-a)


Lesenswert?

De Zordo P. schrieb:
> OIOIOI... ÄÄÄÄm gibts'n Beispiel dafür? :)
> Ich meine ein komplettes, damit ich Zeile für Zeile verstehen kann wie
> die Abfolge genau funktioniert... :/ Bitte?? :O

Der Stack Pointer ist ein Register. Der Compiler kümmert sich darum, 
deshalb sollte man in C nicht daran herumbasteln.

von Patrick Z. (zorpat)


Lesenswert?

Daniel A. schrieb:
> Wenn ein RingBuffer existiert, und die Daten nur ausgewertet werden
> sollen, würde ich die Daten ganz anders auswerten. Zuerst eine schleife
> der form:
>
1
> while(datasAvaiable)
2
>   (*parse)(RS485_getc());
3
>
>
> Danach eine Statemachine zum parsen der daten:
>
1
> 
2
> void parse_slave_address(const char);
3
> 
4
> void(*parse)(const char) = &parse_slave_address;
5
> 
6
> typedef union {
7
>   uint8_t u8[4];
8
>   uint16_t u16[2];
9
>   uint32_t u32;
10
>   int8_t i8[4];
11
>   int16_t i16[2];
12
>   int32_t i32;
13
> } temporary_t;
14
> 
15
> temporary_t tempdatas;
16
> 
17
> typedef struct {
18
>   uint8_t slave_address;
19
>   uint16_t crc;
20
> } required_t;
21
> 
22
> required_t required;
23
> 
24
> enum function_codes_t {
25
>   FUNCTION_A = 0,
26
>   FUNCTION_B = 1,
27
> };
28
> 
29
> void parse_function_code(const char c){
30
>   switch((enum function_codes_t)c){
31
>     case FUNCTION_A:
32
>       parse = &parse_func_a_blabla;
33
>     break;
34
>     case FUNCTION_B:
35
>       parse = &parse_func_b_blabla;
36
>     break;
37
>   }
38
> }
39
> 
40
> void parse_slave_address(const char c){
41
>   required.slave_address = c;
42
>   parse = parse_function_code;
43
> }
44
> 
45
>

Wirklich "lesenswert".. ich vertehe leider fast nur Bahnhof.. ;)
Ich glaube du Programmierst doch schon etwas länger als ich.. ;D

Ne, Scherz..

WARUM will mir denn niemand meinen Ansatz verfolgen und mir ein Beispiel 
geben? :/

Ist doch nicht so schwer, oder?

Nochmals kurz als Erweiterung meiner Erklärung..

- Mein Empfangspuffer (Ringpuffer) wird per Interrupt beschrieben.
- Sofern folgende Bedingungen eintreten (Daten im Ringpuffer > 0 && 
Interframetimeout abgelaufen) dann wird "Modbus_RX_handler(void)" 
aufgerufen

- Im "Modbus_RX_handler(void)" soll nun das Byte 1 und 2 in die 
Variablen geschrieben werden.
1
   modbus_frame->slave_address = RS485_getc();
2
   modbus_frame->function_code = RS485_getc();

- weiters wird nun das 3. Byte gelesen (nur das LSB der "data" Variable 
vorerst.. in einem 2. späteren Schritt wird um 8 Bits 
geschoben..blahblah.. es wären ja 2 Bytes, da uint16_t, aber das 
vernachlässigen wir im Moment)
1
   modbus_frame->data[0] = RS485_getc(); // data[0] = data ohne offset (z.B. 0x01)

"modbus_frame->data[0] = 0x01" würde bedeuten es kommt nur noch 1 
weiteres Datenbyte (eigentlich 2, da uint16, aber das vernachlässigen 
wir aber wieder!)

- Im 4. Byte stehen dann die eigentlichen Nutzdaten
1
   modbus_frame->data[1] = RS485_getc(); // data[2] = data + 1 offset (z.B. 0xFF)

- Im 5. und 6. Byte kommt dann noch das CRC dazu
1
   modbus_frame->crc = (uint16_t)RS485_getc()<<8;
2
   modbus_frame->crc |= RS485_getc();

Und das alles mit "DYNAMISCHER SPEICHERALLOZIIERUNG"! :))
Bitte! :)

von Patrick Z. (zorpat)


Lesenswert?

Folgendes "scheint" bedingt zu Funktionieren..
Ist das so auch richtig Programmiert?
1
void Modbus_RX_handler(void)
2
{
3
   modbus_frame_t *modbus_frame;
4
   modbus_frame = malloc(sizeof(*modbus_frame)); // Allocate memory dynamically
5
   modbus_frame->data = malloc(sizeof(*modbus_frame->data) * 2);
6
7
   modbus_frame->slave_address = RS485_getc();
8
   modbus_frame->function_code = RS485_getc();
9
   RS485_putc(modbus_frame->slave_address);
10
   RS485_putc(modbus_frame->function_code);
11
12
   uint16_t *data_value1 = modbus_frame->data;
13
   *data_value1 = RS485_getc();
14
   RS485_putc(*data_value1);
15
   *data_value1 = RS485_getc(); // Warum wird hier der Pointer automatisch inkrementiert???
16
   RS485_putc(*data_value1);
17
18
   free(modbus_frame->data);
19
   free(modbus_frame);
20
}

von Rene H. (Gast)


Lesenswert?

Wie kommst Du darauf, dass der Pointer automatisch inkrementiert wird?

Wenn ja, stellt sich mir die Frage auch.

Grüsse,
René

von alex (Gast)


Lesenswert?

De Zordo P. schrieb:
> Ist das so auch richtig Programmiert?

Jede Benutzung von malloc ohne ein Betriebssystem mit Speichermanagement 
ist Selbstmord und wird über kurz oder lang zu Problemen führen (hier 
wohl eher "über kurz").

De Zordo P. schrieb:
> WARUM will mir denn niemand meinen Ansatz verfolgen und mir ein Beispiel
> geben? :/

Weil die Mehrheit der hier Beteiligten dich schon darauf hingewiesen 
hat, dass das das falsche Werkzeug ist.

Sieh es so: Du bist im Wasser und wir werfen dir Rettungsringe aus 
schwimmfähigem Material hin, aber du verlangst einen Rettungsring aus 
Zement; ja, er ist rund, aber er wird dir nicht helfen.

------

So genug belehrt, jetzt auch noch etwas Hilfestellung :)

De Zordo P. schrieb:
> *data_value1 = RS485_getc(); // Warum wird hier der Pointer automatisch
> inkrementiert???

Er sollte da nicht inkrementiert werden (und wird es auch nicht). Wie 
kommst du zu dem Schluss?

von Karl H. (kbuchegg)


Lesenswert?

De Zordo P. schrieb:

>    uint16_t *data_value1 = modbus_frame->data;
>    *data_value1 = RS485_getc();
>    RS485_putc(*data_value1);
>    *data_value1 = RS485_getc(); // Warum wird hier der Pointer
> automatisch inkrementiert???

Wird er doch gar nicht.

>    RS485_putc(*data_value1);

Aus welchem Grund benutzt du Pointer Syntax, wenn du gar nicht musst und 
damit auf Kriegsfuss stehst?
1
...
2
   uint16_t *data_value1 = modbus_frame->data;
3
4
   data_value1[0] = RS485_getc();
5
   RS485_putc( data_value1[0] );
6
7
   data_value1[1] = RS485_getc();
8
   RS485_putc( data_value1[1] );
9
10
   free(modbus_frame->data);
11
   free(modbus_frame);
12
...

Du scheinst den Sinn hinter der lokalen temporären Variablen nicht 
verstanden zu haben. Der Sinn dieser Variablen besteht darin, dass du 
Manipulationen an diesem Pointer machen kannst, ohne dir den originalen 
Wert (so wie du ihn von malloc bekommen hast) zu zerstören. Denn du 
brauchst diesen Wert wieder, um ihn an free zu übergeben!
Und nein. Den Pointer schamlos zu inkrementieren um dann hinterher eine 
entsprechende Anzahl an Dekrements zu machen, ist eine ganz blöde Idee.

1) benutzt du Array Index Syntax, um mit dem Pointer zu arbeiten, würde 
man die Hilfsvarable nicht benötigen. Die Schreibweisen mit der 
Hilfsvariablen würden ein wenig einfacher und man kann auch davon 
ausgehen, dass der Compiler sie sowieso wieer rauswerfen wird. Daher: 
benutze die Hilfsvariablen!
Aber wenns denn sein muss, dann kann man natürlich auch dem Compiler 
etwas Arbeit abnehmen, auch wenn der das durchaus selber kann.
1
void Modbus_RX_handler(void)
2
{
3
   modbus_frame_t *modbus_frame;
4
   modbus_frame = malloc(sizeof(*modbus_frame)); // Allocate memory dynamically
5
   uint8_t dataLen = sizeof(*modbus_frame->data) * 2;
6
   modbus_frame->data = malloc( dataLen );
7
8
   modbus_frame->slave_address = RS485_getc();
9
   modbus_frame->function_code = RS485_getc();
10
11
   RS485_putc(modbus_frame->slave_address);
12
   RS485_putc(modbus_frame->function_code);
13
14
   for( uint8_t i = 0; i < dataLen; i++ )
15
   {
16
     modbus_frame->data[i] = RS485_getc();
17
     RS485_putc( modbus_frame->data[i] );
18
   }
19
20
   free( modbus_frame->data );
21
   free( modbus_frame );
22
}

2) du solltest den Rückgabewert von malloc prüfen, ob du auch wirklich 
Speicher bekommen hast. je kleiner der Prozessor umso 'ungerner' benutzt 
man dynamische Allokierung.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Ich geh mal davon aus, dass die ganze Funktion erst mal ein Test ist. 
Denn im Moment ist es völlig sinnlos die Werte zwischenzuspeichern, wenn 
du damit sowieso nichts anderes tust, als sie per RS485 weiterzureichen. 
Da jedes einzelne Byte 1:1 weiter gereicht wird, braucht man da 
überhaupt nichts grossartig zwischenspeichern. Man muss nur wissen, 
wieviele Bytes es sein werden und genau so viele Bytes werden dann vom 
Bus geholt und per RS485 weitergegeben. Das funktioniert sogar dann, 
wenn sich die komplette Anzahl an Bytes erst aus dem Function Code 
ergibt, der empfangen wurde.

: Bearbeitet durch User
von Rene H. (Gast)


Lesenswert?

Karl Heinz schrieb:
> 2) du solltest den Rückgabewert von malloc prüfen, ob du auch wirklich
> Speicher bekommen hast. je kleiner der Prozessor umso 'ungerner' benutzt
> man dynamische Allokierung.

Naja, man weiss das dynamisches allokieren bei einem Mikrocontroller in 
einem Desaster endet. Immer!
Aber diese Erfahrung müssen manche selber machen (ein Grund, weshalb man 
kein klassisches c++ verwenden kann).

Wenn man im Speicherdenken nicht Erfahren ist, dann eh.

Grüsse,
R.

von Patrick Z. (zorpat)


Lesenswert?

Rene H. schrieb:
> Wie kommst Du darauf, dass der Pointer automatisch inkrementiert wird?
>
> Wenn ja, stellt sich mir die Frage auch.
>
> Grüsse,
> René

Ich sende folgende Hex-Bytes via RS485 an den Controller:
AA BB CCDD EEFF (sind also 6 Bytes)
1
modbus_frame->slave_address = 0xAA
2
modbus_frame->function_code = 0xBB
3
modbus_frame->data[0] = 0x00CC
4
modbus_frame->data[1] = 0x00DD
5
modbus_frame->crc = 0xEEFF
Ich schnalls nicht..
Ich inkrementiere den Pointer NICHT und trotzdem bekommen ich die 
richtigen Werte als Echo..
1
   uint16_t *data_value1 = modbus_frame->data;
2
   *data_value1 = RS485_getc();
3
   RS485_putc(*data_value1);
4
   *data_value1 = RS485_getc(); // ==> 0xCC
5
   RS485_putc(*data_value1); // ==> 0xDD

Warum denn das?

von Patrick Z. (zorpat)


Lesenswert?

alex schrieb:
> Jede Benutzung von malloc ohne ein Betriebssystem mit Speichermanagement
> ist Selbstmord und wird über kurz oder lang zu Problemen führen (hier
> wohl eher "über kurz").
>
> De Zordo P. schrieb:
>> WARUM will mir denn niemand meinen Ansatz verfolgen und mir ein Beispiel
>> geben? :/
>
> Weil die Mehrheit der hier Beteiligten dich schon darauf hingewiesen
> hat, dass das das falsche Werkzeug ist.

Ach Mensch.. :/

Mehrere Wege führen nach Rom.. ;)
Warum kann ichs nicht mit Pointern lösen?
Das ist doch keine Hexerei oder? :))))))

von Karl H. (kbuchegg)


Lesenswert?

Rene H. schrieb:
> Karl Heinz schrieb:
>> 2) du solltest den Rückgabewert von malloc prüfen, ob du auch wirklich
>> Speicher bekommen hast. je kleiner der Prozessor umso 'ungerner' benutzt
>> man dynamische Allokierung.
>
> Naja, man weiss das dynamisches allokieren bei einem Mikrocontroller in
> einem Desaster endet. Immer!

Absolut.

Gerade in diesem Beispiel, in dem die Daten in derselben Funktion wieder 
freigegeben werden, ist malloc so unnotwendig wie ein Kropf.

Version 1:
1
void foo()
2
{
3
  uint16_t* pI = malloc( 20 * sizeof(uint16_t) );
4
5
  // stellvertretend für eine Bearbeitung
6
  for( i = 0; i < 20; i++ )
7
    *pI[i] = i;
8
9
  free( pI );
10
}

Version 2:
1
void foo()
2
{
3
  uint16_t pI[20];
4
5
  // stellvertretend für eine Bearbeitung
6
  for( i = 0; i < 20; i++ )
7
    *pI[i] = i;
8
}

Der Unterschied zwischen Version 1 und Version 2 besteht darin, dass 
Version 1 mehr Speicher verbraucht.

> Aber diese Erfahrung müssen manche selber machen.

Absolut. Blöd ist nur, dass sowas schwer zu findende Fehler ergibt. 
Daher der gute Rat, wenigstens den Rückgabewert von malloc zu prüfen, 
auch wenn das nicht alle Fehler abfangt. Wenn der Stack in den Heap 
hineinwächst, dann kann man alle Hoffnung fahren lassen, dass da 
irgendwas sinnvolles passieren wird, wenn der Speicher zu Ende ist.

von Karl H. (kbuchegg)


Lesenswert?

De Zordo P. schrieb:

> Mehrere Wege führen nach Rom.. ;)

Mag sein.
Aber deiner führt in die Hölle und nicht nach Rom.

Wenn der Speicher zu Ende ist, dann ist er zu Ende. Und mit malloc geht 
das schneller als ohne.

von Rene H. (Gast)


Lesenswert?

Das verstehe nun nur begrenzt: du derefenzierst durch die direkte 
Indexierung. Ich verstehe deshalb nicht wo der Pointer inkrementiert 
werden soll.

Grüsse,
René

von Karl H. (kbuchegg)


Lesenswert?

De Zordo P. schrieb:

> Ich inkrementiere den Pointer NICHT und trotzdem bekommen ich die
> richtigen Werte als Echo..

Logisch.
Weil du nur 1 Byte von deinem Data Feld benutzt.
Dort schreibst du das empfangene Byte hinein, echost es sofort und für 
das nächste übertragene Byte benutzt du dann wieder dasselbe 
Speicherbyte.

Logisch, das das alles funktioniert, wenn du nacheinander die Daten alle 
im selben Byte speicherst und sofort echost.

Nur dass du dann eben am Ende nur mit dem zuletzt übertragenen Byte 
dastehst und nicht mit allen.


Um das mal zu veranschaulichen. Was du gemacht hast, ist eine 
komplizierte Version von
1
   uint8_t c;
2
3
   c =  RS485_getc();
4
   RS485_putc( c );
5
6
   c =  RS485_getc();
7
   RS485_putc( c );
8
9
   c =  RS485_getc();
10
   RS485_putc( c );
11
12
   c =  RS485_getc();
13
   RS485_putc( c );
14
15
   c =  RS485_getc();
16
   RS485_putc( c );

selbstverständlich ist dein Echo korrekt. Das Problem beginnt erst dann, 
wenn du ganz zum Schluss den Wert des ersten empfangenen Bytes benötigen 
würdest.

: Bearbeitet durch User
von Alexander I. (a-irrgang)


Lesenswert?

De Zordo P. schrieb:
> alex schrieb:
>> Jede Benutzung von malloc ohne ein Betriebssystem mit Speichermanagement
>> ist Selbstmord und wird über kurz oder lang zu Problemen führen (hier
>> wohl eher "über kurz").
>>
>> De Zordo P. schrieb:
>>> WARUM will mir denn niemand meinen Ansatz verfolgen und mir ein Beispiel
>>> geben? :/
>>
>> Weil die Mehrheit der hier Beteiligten dich schon darauf hingewiesen
>> hat, dass das das falsche Werkzeug ist.
>
> Ach Mensch.. :/
>
> Mehrere Wege führen nach Rom.. ;)
> Warum kann ichs nicht mit Pointern lösen?
> Das ist doch keine Hexerei oder? :))))))

Natürlich kannst du es mit Pointern lösen. Aber Pointer != malloc.

1
typedef struct {
2
  uint8_t slave_address;
3
  uint8_t function_code;
4
  uint16_t *data;
5
  uint16_t crc;
6
} modbus_frame_t;
7
8
struct modbus_frame_t xyz;
9
struct modbus_frame_t* pXyz = &xyz

Arbeite ab dort mit pXyz und du benutzt Pointer, aber ohne dynamische 
Allokierung.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Alexander I. schrieb:

> Arbeite ab dort mit pXyz und du benutzt Pointer, aber ohne dynamische
> Allokierung.

Kann man machen. Muss man aber nicht.
Bringt das irgendwas?
Ausser dass das Ego des Programmierers gestreichelt wird, weil er einen 
Pointer benutzt hat - nein, bringt nichts.

Und Zordo. Genau das ist der springende Punkt. Man benutzt Pointer, wenn 
man muss und wenn es was bringt. Man benutzt sie nicht, weil es so 
grossen Spass macht.

von Alexander I. (a-irrgang)


Lesenswert?

Natürlich nicht. Aber wenn er Pointer so unbedingt benutzen möchte, habe 
ich da nichts dagegen. Mir ging es mehr darum auszudrücken, dass die 
Nutzung von Pointern nicht die dynamische Speicherallokierung erfordert. 
Und die Lösung wird vom OP doch leider massiv forciert und ich versuche 
ihm diesen Spuk auszutreiben ;) Muss ja nicht jeder auf die harte Tour 
lernen :)

Um es ganz klar zu machen:
In meinen Augen ist der erste Schritt zu einer stabilen und effizienten 
Lösung die Entfernung jedweder malloc und zugehöriger free Aufrufe 
(dadruch entfallen die Pointer von selbst). Danach kann man sich dem 
eigentlichen algorithmischen Problem widmen.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

> Die Fragmentierung sollte nicht eintreten, da ich eben bei allen
> void's (momentan nur 1 zum Empfang) den Speicher am Anfang reserviere
> und am Ende wieder freigebe.
> Aktiv werden wohl höchstens 2 Reservierungen meines Structs aktiv sein.

Oh, oh.

Gerade dann läufst du mit fliegenden Fahnen in die Fragmentierung 
hinein.

Der ganze Ansatz ist Quatsch. Da ist es noch besser, definierst die 
selber ein globales Byte-Array, von dem sich die Funktionen ihren Teil 
holen können, wenn was da ist. So hast du wenigstens eine Abschätzung 
darüber, wie klamm der Speicher mit globalen Variablen und Stack 
wirklich ist, und du kannst dir eine Art Handle-basierte 
Speicherverwaltung bauen, so dass du zur Not auch im laufenden Betrieb 
die Daten reorganisieren kannst um aus der Fragmentierungsfalle 
rauszukommen. Einfach ist das allerdings nicht zu programmieren. Da 
sollte man schon recht gut sein, um das hinzukriegen.

: Bearbeitet durch User
von Patrick Z. (zorpat)


Lesenswert?

Karl Heinz schrieb:
> Logisch, das das alles funktioniert, wenn du nacheinander die Daten alle
> im selben Byte speicherst und sofort echost.

ach gott bin ich blöde.. ich bastle solange schon da rum dass ich 
wirklich die EINFACHSTEN Dinge NICHT mehr sehe.. OOOOH MANN!

von Patrick Z. (zorpat)


Lesenswert?

Karl Heinz schrieb:
> Und Zordo. Genau das ist der springende Punkt. Man benutzt Pointer, wenn
> man muss und wenn es was bringt. Man benutzt sie nicht, weil es so
> grossen Spass macht.

Ich verstehe Euch ja..
ABER(!!), wenn ich dann z.B. in einer weiteren Routine auf den selben 
"Speicher" (Struct) zugreifen möchte kann ich doch einfach einen Pointer 
übergeben und muss nichts neu anlegen und kann den "alten" Struct im 
Speicher direkt ansprechen..

So etwa wie:
1
void RS485_puts(const char *s)
2
{
3
  while(*s)
4
  {
5
    RS485_putc(*s++);
6
  }
7
}

Das Programm arbeitet nicht nur dieses void ab! Im Moment ja, aber 
später macht der µC viele weitere Sachen wenn keine neuen Daten vom Bus 
anstehen..
Deshalb dachte ich mir eben... Pointer sind die Lösung, da man alles nur 
als Speicheraddressen sieht.

Ist das nicht der BESTE WEG? :/

Gruß!

von Karl H. (kbuchegg)


Lesenswert?

De Zordo P. schrieb:

> So etwa wie:
>
1
> void RS485_puts(const char *s)
2
> {
3
>   while(*s)
4
>   {
5
>     RS485_putc(*s++);
6
>   }
7
> }
8
>


Aber auch diese Funktion muss man nicht so benutzen
1
...
2
  char* data = malloc( 5 ),
3
  data[0] = 't';
4
  data[1] = 'e';
5
  data[2] = 's';
6
  data[3] = 't';
7
  data[4] = '\0';
8
9
  RS485_puts( data );
10
11
  free( data );

man kann, darf und soll die Funktion ruhig auch so benutzen:
1
  char data[] = "test";
2
  RS485_puts( data );

bzw. gleich überhaupt
1
  RS485_puts( "test" );


Nur weil eine Funktion einen Pointer haben will, bedeutet das nicht, 
dass der Aufrufer den entsprechenden Speicher dazu dynamisch allokiert 
haben muss.

> Das Programm arbeitet nicht nur dieses void ab! Im Moment ja, aber
> später macht der µC viele weitere Sachen wenn keine neuen Daten vom Bus
> anstehen..
> Deshalb dachte ich mir eben... Pointer sind die Lösung, da man alles nur
> als Speicheraddressen sieht.

Der Pointer ist nicht das eigentliche Problem. Der malloc ist das 
Problem! DU kannst, wenn es blöd läuft, in Summe genug Speicher frei 
haben aber nicht genug freien Speicher am Stück, um die Anforderung zu 
bedienen!

Beispiel.
Du hast 60 Bytes freien Speicher.
1
(jedes Kästchen steht für 10 Bytes)
2
     +--+--+--+--+--+--+
3
     |  |  |  |  |  |  |
4
     +--+--+--+--+--+--+

Die erste Anforderung benötigt 10 Bytes. Also werden 10 Bytes reserviert
1
     +--+--+--+--+--+--+
2
     |XX|  |  |  |  |  |
3
     +--+--+--+--+--+--+

Während diese Anforderung noch besteht, kommt die nächste Anforderung 
mit 40 Bytes. Auch diese 40 Bytes werden reserviert
1
     +--+--+--+--+--+--+
2
     |XX|XX|XX|XX|XX|  |
3
     +--+--+--+--+--+--+

Die Bearbeitung der ersten Daten ist erledigt und die 10 Bytes werden 
frei gegeben.
1
     +--+--+--+--+--+--+
2
     |  |XX|XX|XX|XX|  |
3
     +--+--+--+--+--+--+

Die nächste Anforderung braucht 15 Bytes.
rein rechnerisch hast du 20 Bytes frei, denn von den 60 Bytes total sind 
gerade 40 Bytes belegt.

Trotzdem, schau auf die Zeichnung, du hast keine 15 Bytes. Du hast zwar 
20 Bytes frei, aber keine 15 Bytes am Stück! Die 10 freien Bytes vor der 
Reservierung und die 10 Bytes nach der bestehenden Reservierung helfen 
dir nichts.

von Patrick Z. (zorpat)


Lesenswert?

Karl Heinz schrieb:
> Beispiel.
> Du hast 60 Bytes freien Speicher.(jedes Kästchen steht für 10 Bytes)
>      +--+--+--+--+--+--+
>      |  |  |  |  |  |  |
>      +--+--+--+--+--+--+
>
> Die erste Anforderung benötigt 10 Bytes. Also werden 10 Bytes reserviert
> +--+--+--+--+--+--+
>      |XX|  |  |  |  |  |
>      +--+--+--+--+--+--+
>
> Während diese Anforderung noch besteht, kommt die nächste Anforderung
> mit 40 Bytes. Auch diese 40 Bytes werden reserviert
> +--+--+--+--+--+--+
>      |XX|XX|XX|XX|XX|  |
>      +--+--+--+--+--+--+
>
> Die Bearbeitung der ersten Daten ist erledigt und die 10 Bytes werden
> frei gegeben.     +--+--+--+--+--+--+
>      |  |XX|XX|XX|XX|  |
>      +--+--+--+--+--+--+
>
> Die nächste Anforderung braucht 15 Bytes.
> rein rechnerisch hast du 20 Bytes frei, denn von den 60 Bytes total sind
> gerade 40 Bytes belegt.
>
> Trotzdem, schau auf die Zeichnung, du hast keine 15 Bytes. Du hast zwar
> 20 Bytes frei, aber keine 15 Bytes am Stück! Die 10 freien Bytes vor der
> Reservierung und die 10 Bytes nach der bestehenden Reservierung helfen
> dir nichts.

DANKE DANKE DANKE! Diese Ausführung bringt mein mentales Chaos doch mal 
in eine gerade Linie! :)

Ich habe meinen Code mal wie folgt "umgeschrieben"..

Was noch fehlt ist die Abfrage ob das Alloziieren überhaupt geklappt 
hat!!
1
typedef struct {
2
  uint8_t slave_address;
3
  uint8_t function_code;
4
  uint16_t *data;
5
  uint16_t crc;
6
} modbus_frame_t;
7
8
-----------------------------------------------------------
9
10
void Modbus_RX_handler(void)
11
{
12
  if (RS485_new_packet)
13
  {
14
    modbus_frame_t *modbus_frame;
15
    modbus_frame = malloc(sizeof(*modbus_frame)); // Ist das so OK und brauchbar?
16
    
17
    modbus_frame->slave_address = RS485_getc();
18
    modbus_frame->function_code = RS485_getc();
19
    RS485_putc(modbus_frame->slave_address);
20
    RS485_putc(modbus_frame->function_code);
21
22
    modbus_frame->data = malloc(sizeof(*modbus_frame->data) * 3); // Ist das so OK?
23
    
24
    uint16_t *data_value = modbus_frame->data;
25
    data_value[0] = RS485_getc();
26
    RS485_putc(data_value[0]);
27
28
    data_value[1] = RS485_getc();
29
    RS485_putc(data_value[1]);
30
    
31
    modbus_frame->crc = (uint16_t)RS485_getc()<<8;
32
    modbus_frame->crc |= RS485_getc();
33
    RS485_putc(modbus_frame->crc>>8);
34
    RS485_putc(modbus_frame->crc);
35
    
36
    /* TESTING */
37
    RS485_putc(RS485_new_packet);
38
    RS485_putc(sizeof(*modbus_frame));
39
    RS485_putc(sizeof(*modbus_frame->data));
40
    /* ------- */
41
    
42
    free(modbus_frame->data);
43
    free(modbus_frame);
44
    RS485_new_packet = 0; // reset "new packet" flag
45
  }
46
}

Soweit so gut, also es funktioniert bis hierher.. (Ich HOFFE auch 
"immer", nicht dass ich irgendwann meine Variable ins "NICHTS" pointe.. 
:D

Zur Fragmentierung..
Wenn ich nun aber wirklich nur 2 Funktionen habe welche Speicher 
dynamisch alloziieren, dann kann ich das doch einfach Überschauen.. also 
ich meine z.B.
- 15 Byte in "Modbus_RX_handler(void)" alloziieren (diese 15 Bytes 
werden irgendwann mal mit "free()" freigegeben)
- 20 Bytes in "Modbus_TX_handler(void)" alloziieren (diese 20 Bytes 
werden irgendwann mal mit "free()" freigegeben)

Der Kompiler sagt mir ich hätte noch 50 Bytes freien RAM-Speicher, somit 
komme ich nicht "außerhalb" des maximalen Speichers..oder?

Auf was muss ich aber nun achten? Wenn ich irgendwo lokale Variablen 
benutze, kann es denn dann passieren, dass z.B. gerade diese in meine 35 
Bytes "reinpfuschen"?

Viele Fragen, doch ich muss es genau wissen bevor ich mich an die 
Codeausarbeitung wage.

: Bearbeitet durch User
von Patrick Z. (zorpat)


Lesenswert?

Karl Heinz schrieb:
> Mag sein.
> Aber deiner führt in die Hölle und nicht nach Rom.

xD

von Patrick Z. (zorpat)


Lesenswert?

Karl Heinz schrieb:
> man kann, darf und soll die Funktion ruhig auch so benutzen:
>   char data[] = "test";
>   RS485_puts( data );
>
> bzw. gleich überhaupt  RS485_puts( "test" );
>
> Nur weil eine Funktion einen Pointer haben will, bedeutet das nicht,
> dass der Aufrufer den entsprechenden Speicher dazu dynamisch allokiert
> haben muss.

Ja korrekt.. das mache ich auch immer so, dass ich die 
"Kurzschreibweise"
1
   RS485_puts( "test" );
beim Aufruf benutze. :)

von Alexander I. (a-irrgang)


Lesenswert?

Die Lösung mit malloc wird dich pro malloc 2 Byte Speicher kosten 
[http://www.nongnu.org/avr-libc/user-manual/malloc.html - Abschnitt 
"Implementation details"]. Und zusätzlich Nerven beim debuggen 
unerklärlicher Fehler. Weiterhin wird es deinen Code unnötig 
verkomplizieren.

Das angehängte Beispiel tut im Prinzip das selbe und zeigt dir, wie du 
auf den selben "handler-lokalen Speicher" auch in weiteren 
Unterfunktionen zugreifen kannst.
Da deine Handler-Funktion nur einmal aufgerufen wird ist der Speicher 
nur so lange auf dem Stack belegt, wie die Funktion läuft und ist danach 
weg.
1
static const uint8_t MAX_DATA = 2;
2
3
typedef struct {
4
  uint8_t slave_address;
5
  uint8_t function_code;
6
  uint16_t data[MAX_DATA];
7
  uint16_t crc;
8
} modbus_frame_t;
9
10
11
void Modbus_RX_handler()
12
{
13
  // Hier ist modbus_frame lokal in der Funktion auf dem Stack
14
  // Jeder Aufruf der Funktion = Platz auf dem Stack angelegt.
15
  struct modbus_frame_t modbus_frame;
16
17
  modbus_frame.slave_address = RS485_getc();
18
  modbus_frame.function_code = RS485_getc();
19
20
  RS485_putc(modbus_frame.slave_address);
21
  RS485_putc(modbus_frame.function_code);
22
23
  for(uint8_t i = 0; i < MAX_DATA; i++)
24
  {
25
    modbus_frame.data[i] = RS485_getc();
26
    RS485_putc(modbus_frame.data[i]);
27
  }
28
   
29
  verify_crc(&modbus_frame);
30
  func2(&modbus_frame);
31
}
32
33
void verify_crc(struct modbus_frame_t* pModbus_frame)
34
{
35
  // Hier arbeitest du auf der selben Variable (dem selben Speicher) wie im Handler
36
  // du hast nicht erneut Speicher für das struct verbraucht
37
  ... tu was mit modbus_frame...
38
  ... z.B.: unit16_t crc = pModbus_frame->data[0] ^ pModbus_frame->data[1]; ...
39
}
40
41
void func2(struct modbus_frame_t *pModbus_frame)
42
{
43
  ... tu was anderes mit modbus_frame...
44
}

: Bearbeitet durch User
von Patrick Z. (zorpat)


Lesenswert?

Alexander I. schrieb:
> static const uint8_t MAX_DATA = 2;

MAX_DATA weiß ich nicht vor Abarbeitung der ersten 2 Bytes.. MAX_DATA 
kann auch 20 oder mehr Bytes bzw. uint16 sein.. :/

> uint16_t data[2];

Hier das Selbe.. ich kann vor Abarbeitung der ersten 2 Bytes nicht sagen 
ob ich mit 2 x uint16_t oder 10 x uint16_t auskomme..

Deshalb dachte ich eben ich mache das STRUCT und definiere dann erst den 
"uint16_t data[xxxx];" sobald ich weiß wie große denn mein Datenstrom 
sein wird! :)

Oder kann bzw. soll ich die Definition von Struct auch während der 
Laufzeit machen?

: Bearbeitet durch User
von Alexander I. (a-irrgang)


Lesenswert?

Dann belegst du MAX_DATA mit deinem definierten Maximum. Auch dynamisch 
wirst du diese Zahl nie überschreiten können. Im Gegenteil: durch den 
"Speicherverlust" pro malloc gewinnst du so gegenüber der 
malloc-Variante sogar ein paar Bytes.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

De Zordo P. schrieb:
> Alexander I. schrieb:
>> static const uint8_t MAX_DATA = 2;
>
> MAX_DATA weiß ich nicht vor Abarbeitung der ersten 2 Bytes.. MAX_DATA
> kann auch 20 oder mehr Bytes bzw. uint16 sein.. :/
Was du nicht weisst, das ist wieviele Btes du zur Bearbeitung einer 
Busmessage tatsächlich benötigst.

Du definierst aber, wie lange eine Busmessage MAXIMAL sein kann.
D.h. jede MEssage hat mglw. ein wenig Speicherverschnitt, den es aus dem 
Array theoretisch nicht benötigt.

Aber: Es ist immer noch besser eine Maximal-Größe garantiert bedienen zu 
können, als (wenn es blöd läuft) bei kleineren Messages schon W.O. geben 
zu müssen.

Ein µC ist kein Desktop Rechner. Ein µC muss immer zuverlässig 
funktionieren. Eine Meldung "Momentan stehen keine Resourcen zur 
Verfügung, versuchen sie die Operation später noch einmal" ist auf einem 
irgendwo eingebauten µC nicht akzeptabel. Da ist es schon besser, wenn 
seine mögliche Leistung etwas geringer als das theoretische Maximum ist, 
wenn dafür diese Leistung aber 100% garantiert ist und nicht auf einem 
wackeligem "ja nach Wasserstand in der Donau kann das funktionieren oder 
auch nicht" beruht.

Hast du 60 Bytes Speicher zur Verfügung und kannst du maximal 2 Messages 
im Speicher haben, dann kann jede Message ein Maximum von 30 Bytes 
beanspruchen. Und zwar immer und zu jedem Zeitpunkt. Das ist zwar 
schlechter, als die Lösung, in der auch eine Message mit 40 Bytes 
verarbeitet werden könnte aber immer noch besser als die streikende 
Variante, in der du eine 15 Byte Message nicht mehr behandeln kannst, 
weil der Speicher fragmentiert ist.

: Bearbeitet durch User
von Patrick Z. (zorpat)


Lesenswert?

Alexander I. schrieb:
> Dann belegst du MAX_DATA mit deinem definierten Maximum. Auch dynamisch
> wirst du diese Zahl nie überschreiten können. Im Gegenteil: durch den
> "Speicherverlust" pro malloc gewinnst du so gegenüber der
> malloc-Variante sogar ein paar Bytes.

Hm... also soll ich wirklich ein Maximum definieren welches "nie" 
komplett erreicht werden kann, ok. Es klingt vernünftig, da ich 
ansonsten eh den Speicher doppelt belegen würde wenn ich kein 
Speichermanagement programmiere.. und dann geht mir der Flash auch aus, 
denn für das Speichermanagement gehen wohl auch einige Bytes drauf.. ;)

von Bastler (Gast)


Lesenswert?

MAX_DATA hat seinen Namen davon, daß es das Maximum der möglichen 
Datenlänge ist. Das ist fix!

von Patrick Z. (zorpat)


Lesenswert?

Karl Heinz schrieb:
> Ein µC ist kein Desktop Rechner. Ein µC muss immer zuverlässig
> funktionieren. Eine Meldung "Momentan stehen keine Resourcen zur
> Verfügung, versuchen sie die Operation später noch einmal" ist auf einem
> irgendwo eingebauten µC nicht akzeptabel. Da ist es schon besser, wenn
> seine mögliche Leistung etwas geringer als das theoretische Maximum ist,
> wenn dafür diese Leistung aber 100% garantiert ist und nicht auf einem
> wackeligem "ja nach Wasserstand in der Donau kann das funktionieren oder
> auch nicht" beruht.

Richtig!
Ich glaub der Lösungsansatz von Alexander ist sehr gut geeignet.. und 
das MAX_DATA muss ich einfach definieren.. größer als mein 
RS485_RX_BUFFER kanns eh nicht sein.. ;)

Sollte ich eigentlich meinen Ringpuffer bereits mit dem Struct versehen 
damit ich keinen "doppelten" Speicher benötige, oder?

von Patrick Z. (zorpat)


Lesenswert?

Meine Empfangsroutine in der "RS485.c"-Datei:

1
ISR(USART_RX_vect)
2
{
3
  uint8_t tmp_head = 0;
4
  tmp_head = (rx_head + 1) % RX_BUFFER_SIZE;
5
  if (tmp_head == rx_tail) {
6
    // buffer overflow error!
7
    RS485_clear_RX_buffer();
8
  } else 
9
  {
10
    rx_buffer[rx_head] = UDR;
11
    //RS485_putc(UDR); // DEBUG
12
    rx_head = tmp_head;
13
    RS485_packet_complete_timeout = SetDelay(RS485_PACKET_TIMEOUT); // set new hard timeout value
14
    RS485_hard_packet_timeout_value = SetDelay(RS485_TIMEOUT); // set new inter frame timeout value
15
  }
16
}

Könnte ich nun in der "Modbus.h"-Datei mein Struct als "extern" 
definieren und direkt in der "ISR(USART_RX_vect)" mein Struct füllen?
Im "Modbus_RX_handler" kann ich dann im Prinzip "nur" mehr parsen..?

:)

von Alexander I. (a-irrgang)


Lesenswert?

Im Header sollte man nichts als extern definieren mit Ausnahme von "C", 
das auf Controllern aber irrelevant ist.

An der Stelle müsste dein Interrupthandler deutlich mehr Logik bekommen, 
um zu wissen, was das empfangene Byte denn ist und wo es in die Struktur 
hin gehört.

Im Prinzip gefällt mir dein Interrupthandler also von seinen Aufgaben so 
wie er ist ganz gut. Ich würde dir empfehlen die Umsetzung mit diesem 
Interrupthandler zu starten und bei der Implementierung eine Anpassung 
im Hinterkopf zu behalten, so dass du dich in keine Sackgasse 
entwickelst.

Solltest du dann wirklich an die Grenzen stoßen, kann man über 
Optimierungen am Interrupthandler nachdenken.

von Patrick Z. (zorpat)


Lesenswert?

Alexander I. schrieb:
> Im Header sollte man nichts als extern definieren mit Ausnahme von "C",
> das auf Controllern aber irrelevant ist.
>
> An der Stelle müsste dein Interrupthandler deutlich mehr Logik bekommen,
> um zu wissen, was das empfangene Byte denn ist und wo es in die Struktur
> hin gehört.
>
> Im Prinzip gefällt mir dein Interrupthandler also von seinen Aufgaben so
> wie er ist ganz gut. Ich würde dir empfehlen die Umsetzung mit diesem
> Interrupthandler zu starten und bei der Implementierung eine Anpassung
> im Hinterkopf zu behalten, so dass du dich in keine Sackgasse
> entwickelst.
>
> Solltest du dann wirklich an die Grenzen stoßen, kann man über
> Optimierungen am Interrupthandler nachdenken.

Vielen Dank für das Lob! (Sonst gabs heute fast nur Tadel.. :P)

Mein ISR hat schon einiges an Logik bekommen..
1. Hard Timeout zwischen 2 Übertragungen - Pakete löschen (NICHT 
BLOCKIERENDER Aufruf!)
1
   RS485_packet_complete_timeout = SetDelay(RS485_PACKET_TIMEOUT);
Es passiert manchmal, dass ein Master im Netzwerk ein Paket nicht 
vollständig Überträgt da z.B. gerade ein neuer Busteilnehmer im Betrieb 
auf den Bus aufgeschaltet worden ist. (Sozusagen "Hot-Swap"). Dabei kann 
es sein, dass einige der Clients nur einen Teil eines gesamten 
Datenstrings empfangen haben. Der µC würde ewig in externen 
Programmschleifen stehenbleiben. Durch meinen Code wird das Paket aber 
nach 500ms einfach Verworfen. Diese Zeit ist ja eine Ewigkeit in der 
Kommunikationsschicht mehrerer Controller.

2. Soft Timeout, auch "Inter Frame Timeout" genannt (NICHT BLOCKIERENDER 
Aufruf!)
1
   RS485_hard_packet_timeout_value = SetDelay(RS485_TIMEOUT);
Dieser Timeout ruft den eigentlichen "Modbus_RX_handler()" auf der dann 
die gelesenen Bytes parst. Der Timeout läuft 5ms nach dem Empfang des 
letzten Bytes bzw. Frame ab. Bei 19200 Baud ist das mehr als genug..

Das Besondere, das ganze funktioniert OHNE das Programm zu BLOCKIEREN..
Hierzu generiere ich mit Hilfe des Timer1 einen 1ms Interrupt der im 
Hintergrund eine Laufvariable hochzählt..

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.