Forum: Compiler & IDEs Header vor die Nutzdaten schreiben


von Karlo (Gast)


Lesenswert?

Morgen,

ich habe hier zum wiederholten Male das selbe Problem.
Mir fällt da keine elegante Lösung ein, aber vielleicht wisst ihr ja 
was.

Ich habe eine Sendefunktion die einen Zeiger auf Daten und eine 
Längenangabe erwartet.
void senden(uint8_t *src, uint8_t length);
Ob das jetzt UART, Ethernet, SPI oder sonstwas ist spielt erstmal keine 
Rolle.

Eine Applikation möchte Nutzdaten senden.
Allerdings gehören diese noch in ein Protokoll verpackt.
Je nach Medium meinetwegen eine Längenangabe, SOF oder Empfängeradresse.
D.h. ich muss die Nutzdaten die ich bekomme um einen Header (und 
manchmal auch Trailer) erweitern.

Das heißt aber für mich als "Protokollschicht" dass ich erstmal alle 
Daten, die mir die Applikation gibt in einen temporären Buffer kopieren 
muss.
Der Buffer ist um den Protokolloverhead größer zu wählen als die 
Nutzdaten.

Auf einem uC läuft das wohl auf einen globalen Puffer der Form "uint8_t 
tmpBuf[MAX_FRAME_SIZE]" hinaus.
Also kommt zur Kopierarbeit noch ein permanenter RAM-Verbrauch hinzu.

Die Ressourcen (RAM + Rechenzeit) hab ich zwar noch locker über. Aber 
mir gehts darum, diese Problemstellung in Zukunft geschickter zu lösen 
als bisher immer.

Wie würdet ihr das lösen?

Viele Grüße,
Karlo

von Peter II (Gast)


Lesenswert?

Karlo schrieb:
> Wie würdet ihr das lösen?

warum ruft du die Sendefunktion nicht 2mal auf? einmal für den header 
und dann noch mal für die Daten.

von Karlo (Gast)


Lesenswert?

Das würde eigentlich nur bei UART funktionieren.

Bei allen Medien, die Paketorientiert arbeiten (oder vielleicht selbst 
ein Protokoll implementieren) wie I²C, Ethernet, usw. wird das so 
nichts.

von Peter D. (peda)


Lesenswert?

Karlo schrieb:
> Der Buffer ist um den Protokolloverhead größer zu wählen als die
> Nutzdaten.

Nö, der Puffer enthält nur den Header.
Die Daten sendest Du direkt aus deren Quelle.
Und falls gewünscht wird dabei auch gleich die CRC byteweise berechnet 
und dann im Footer angehängt.

von Peter II (Gast)


Lesenswert?

Karlo schrieb:
> Bei allen Medien, die Paketorientiert arbeiten (oder vielleicht selbst
> ein Protokoll implementieren) wie I²C, Ethernet, usw. wird das so
> nichts.

klar geht das auch bei Ethernet. Der STack packet die Packet eh nach 
seinen eingenen Algorithmus

von Karlo (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Nö, der Puffer enthält nur den Header.
> Die Daten sendest Du direkt aus deren Quelle.

Wie soll das gehen?
Hier Beitrag "Re: Header vor die Nutzdaten schreiben" hab ich doch 
schon geschrieben, warum ich die Sendefunktion nur einmal aufrufen darf.

Konkret bei I²C würden sonst zwei Botschaften generiert.
Was wenn der Slave erwartet, dass das erste Byte ein Kommando ist?
Bei zwei seperaten Übertragungen würde das erste Byte der Nutzdaten als 
Kommando interpretiert.

Oder hab ich dich falsch verstanden und du löst das mit nur einem 
Aufruf?
Dann muss ich aber wieder vorher puffern.

> klar geht das auch bei Ethernet. Der STack packet die Packet eh nach
> seinen eingenen Algorithmus
Was ist denn "der Stack"?
Vor einiger Zeit hatte ich in einem Projekt mit lwIP und UDP zu tun.
Die udp_send() haut da auch einfach nur die Daten raus die sie von mir 
bekommt.

von Klaus W. (mfgkw)


Lesenswert?

Man könnte zu der Sendefunktion noch eine weitere schreiben, die einen 
dazu passenden Puffer bereitstellt, intern jedoch (für den Header) einen 
etwas größeren beschafft, wie auch immer. Wenn mehrere solche Puffer im 
Umlauf sind, dann wird halt für jeden ein Handle vergeben (das intern 
letztlich auch einfach ein Zeiger auf den ganzen Block sein kann).
Der Nutzer sieht dann nur das Handle und komm darüber an die Nutzdaten, 
ohne den Header davor sehen zu müssen.

Beispiel:
1
#include <stdlib.h>
2
#include <stddef.h>
3
#include <stdio.h>
4
#include <string.h>
5
6
// gesamtes Paket: Header + Nutzdaten
7
typedef struct
8
{
9
    // Header:
10
    size_t   lenPayload;
11
    // ... und evtl. weitere Headerdaten ...
12
13
    // Nutzdaten:
14
    char     payload[0];
15
}   myProtocolPacket_t;
16
17
// als Handle für ein solches Paket einfach den Zeiger darauf nehmen:
18
typedef myProtocolPacket_t   * myProtocolHandle_t;
19
20
// beschafft einen Puffer für (len) Byte Nutzdaten:
21
myProtocolHandle_t myProtocolGetBuffer( size_t len )
22
{
23
    myProtocolHandle_t handle = (myProtocolHandle_t)calloc( 1, sizeof(myProtocolPacket_t) + len );
24
    if( handle )
25
    {
26
        handle->lenPayload = 10;
27
        return handle;
28
    }
29
    else
30
    {
31
        return NULL;
32
    }
33
}
34
35
void myProtocolFreeBuffer( myProtocolHandle_t paket )
36
{
37
    free( paket );
38
}
39
40
41
int myProtocolSend( myProtocolHandle_t paket )
42
{
43
    // evtl. Header aktualisieren...
44
    // ...
45
46
    // gesamtes Paket versenden...
47
    // ... send( ..., paket, sizeof( myProtocolHandle_t ) + paket->lenPayload, ... );
48
49
    return 0;
50
}
51
52
int main( int nargs, char **args )
53
{
54
    // Paket für 10 Byte Nutzdaten:
55
    myProtocolHandle_t    meinPaket = myProtocolGetBuffer( 10 );
56
    strncpy( meinPaket->payload, "Hallo", 10 );
57
    myProtocolSend( meinPaket );
58
    myProtocolFreeBuffer( meinPaket ); meinPaket = NULL;
59
60
    return 0;
61
}

Der Anwender hat nur noch mit dem Datentyp des Handle zu tun, und mit 
dem Feld payload in der struct.
Der Header geht ihn nichts an.

Trotzdem muß nie etwas umkopiert werden, weil der Header von Anfang 
existiert.

(In C++ würde es natürlich mit private etc. schöner gehen...)

von Karl H. (kbuchegg)


Lesenswert?

Karlo schrieb:
> Peter Dannegger schrieb:
>> Nö, der Puffer enthält nur den Header.
>> Die Daten sendest Du direkt aus deren Quelle.
>
> Wie soll das gehen?
> Hier Beitrag "Re: Header vor die Nutzdaten schreiben" hab ich doch
> schon geschrieben, warum ich die Sendefunktion nur einmal aufrufen darf.

Dann musst du eben deine Sendefunktion etwas überarbeiten.

> Konkret bei I²C würden sonst zwei Botschaften generiert.
> Was wenn der Slave erwartet, dass das erste Byte ein Kommando ist?

Dann schick es ihm!

> Bei zwei seperaten Übertragungen würde das erste Byte der Nutzdaten als
> Kommando interpretiert.

Moment!
Kein Mensch sagt, dass ein Aufruf deiner Sendefunktion eine 'unteilbare' 
Einheit ist. Du musst dann eben das Versenden einer Nachricht selber 
wieder in ein paar Einzelteile auflösen

So nach dem Muster
1
void sendMsg( const char* Msg )
2
{
3
  startMsg();   // mach alles was an Vorgeplänkel auf den Leitungen
4
                // notwendig ist
5
  sendBytes( Header );   // dann die Bytes hinten nach, die den Header
6
                         // ausmachen
7
  sendBytes( Msg );      // die Nutzdaten
8
9
  stopMsg();             // und das wars.
10
}

Deine Funktion sendBytes darf eben NICHT eine Message verschicken (oder 
was uach immer dein Paket ausmacht) sondern tatsächlich nur das tun, was 
der Funktionsname ausmacht: die übergebenen Bytes auf den Weg bringen. 
Das ganze möglicherweise notwendige Vorgeplänkel mit Verbindungsaufbau 
etc. wird NICHT von dieser Funktion erledigt.
Du übergibst halt deiner Treiberstufe deine 'Nachricht' nicht als 
unteilbaren Datenklumpen, in dem schon alles fix fertig aufbereitet sein 
muss, sondern in kleineren Dosen, die dann eben vom Sender zu keinem 
einzigen Zeitpunk zu einem kompletten Datenklumpen zusammengesetzt 
werden, sondern in diesen Einheiten abgesetzt werden. (Oder eben wie im 
Fall von Ethernet vom TCP/IP Stack sowieso erst mal gesammelt und 
aufbereitet werden. Aber das heißt ja nicht, dass die höherliegenden 
SW-Schichten das auch schon vorab machen müssen. Auch einen TCP/IP Stack 
darf man mit "Einzelteilen" füttern. Der Empfänger sieht ja sowieso 
nicht, ob ich das vorab erst mal alles zusammengesetzt habe oder nicht.

von Peter D. (peda)


Lesenswert?

Karlo schrieb:
> Konkret bei I²C würden sonst zwei Botschaften generiert.

Man muß natürlich den I2C-Handler entsprechend schreiben. Er kriegt 
einen Pointer auf den Header übergeben und an dessem Ende steht ein 
Pointer auf die Daten. Man braucht ja eh eine Statemaschine und die hat 
dann einen State mehr (sende Daten).

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.