Forum: Compiler & IDEs OOP in C - Problem mit Funktionspointern


von Rahul D. (rahul)


Lesenswert?

Moin,
ich habe folgenden Aubau:
An einer Steuerung (STM32F4) hängen verschiedene gleichartige Geräte 
(z.B. Netzteile).
Diese Geräte werden über RS232, USB-CDC oder I²C angesprochen.
Ich würde mir nun gerne eine Art Treiber scheiben (Keil C, RTOS u.a. 
wegen USB-Host), der dafür sorgt, dass das übergeordnete Programm alle 
Geräte gleich ansprechen kann.
Da gleichzeitig mehrere der Geräte am der Steuerung hängen können, 
verwalte ich die Dinger mit einem Array.
Das Array ist eine Auflistung von struct, die Funktionspointer enthält.
Die Funktionspointer zeigen dann auf die Funktionen, die zum Steuern des 
Geräts benötigt werden.

Wie realisiert man das?
Bisher habe ich Funktionen, denen noch die Schnittstelle mit übergeben 
wird. Das würde ich mir - wenn möglich - gerne sparen und die Funktionen 
nur so aufrufen:

Geraet[1]->Licht(AN).

Geraet[1] sollte dann halt "wissen", welche Schnittstelle verwendet 
werden soll. Geraet[0] könnte eine andere Schnittstelle oder im Fall von 
I²C eine andere Adresse oder beide sein.

Moentan würde es so aussehen:
Geraet[1]->Licht(Schnittstelle[1],AN)
oder
Geraet[1]->Licht(Geraet[1]->Schnittstelle,AN)

PS: Dass man das auch in C++ erledigen kann, ist mir klar. C ist aber 
"Kundenvorgabe".

von hust (Gast)


Lesenswert?

sowas kann man machen
.. muss man aber nicht ^^


z.B.
linuxlike gibt es diverse I/O funktionen für devices
also read/write/open/close/ioctl

gibt dazu sogar schon diverse abstractionen zu wo man geräte mit 
"dev/..." anspricht

entweder macht man nun auch structs im treiber so das man mehrere 
intanzen des treibers laden kann ...
wo halt adressen und so gespeichert werden..

jede geraetestruct merkt sich dann die erzeugt instanz im treiber
und greift darauf zu


oder wenn die peripherie überschaubar ist , dann adressparameter in die 
geraetestruct ...

von Hans (Gast)


Lesenswert?

Bei einer C++-Klasse würde implizit der this-Pointer an die Funktion 
übergeben werden. In C musst Du das explizit hinschreiben:
1
Geraet[1]->Licht(&Geraet[1], AN);

Die Schnittstelle ist dann ein Element in der Gerät-Struktur, auf die 
die Funktion "Licht" zugreift.

Wenn Dich das redundante "Geraet[1]" stört, könntest Du den Aufruf 
höchstens in ein Makro verpacken, so dass es dann in etwa so aussieht:
1
LICHT(Geraet[1], AN);

von hust (Gast)


Lesenswert?

oder du baust dir macros ...

um aus

Geraet[1]->Licht(Geraet[1]->Schnittstelle,AN)

ein


Geraet[1]->Lichtmacro(AN)

zu erzeugen

von Carl D. (jcw2)


Lesenswert?

> PS: Dass man das auch in C++ erledigen kann, ist mir klar. C ist aber
> "Kundenvorgabe".

Da könnte man ja auch ganz fies sein und C-Front (den alten C++ nach C 
Compiler) auskramen. Wenn der Kunde doch C++ in C will.

LLVM könnte man für sowas sicher auch einsetzen, damit wurde ja auch ein 
C++ nach JavaScript Compiler gebaut.

Mal gesucht und gefunden: 
http://llvm.org/releases/3.1/docs/FAQ.html#translatecxx

von Jobst Q. (joquis)


Lesenswert?

Rahul D. schrieb:
> Bisher habe ich Funktionen, denen noch die Schnittstelle mit übergeben
> wird. Das würde ich mir - wenn möglich - gerne sparen und die Funktionen
> nur so aufrufen:
>
> Geraet[1]->Licht(AN).

Ich würde Gerät 1 nur so aufrufen wollen:

schalte(1,AN);
oder
schalte(1,AUS);

Den ganzen Kram mit den Arrays und Schnittstellenzuordnung brauchst du 
dann nur einmal, eben in der Funktion schalte(geraetenummer,an_aus).

von hust (Gast)


Lesenswert?

Jobst Q. schrieb:
> Rahul D. schrieb:
>> Bisher habe ich Funktionen, denen noch die Schnittstelle mit übergeben
>> wird. Das würde ich mir - wenn möglich - gerne sparen und die Funktionen
>> nur so aufrufen:
>>
>> Geraet[1]->Licht(AN).
>
> Ich würde Gerät 1 nur so aufrufen wollen:
>
> schalte(1,AN);
> oder
> schalte(1,AUS);
>
> Den ganzen Kram mit den Arrays und Schnittstellenzuordnung brauchst du
> dann nur einmal, eben in der Funktion schalte(geraetenummer,an_aus).

das ist auch schick ..

ICH würde aber die "1"  durch eine pinter auf die gerätestruct ersetzen.

geht  sicherach benennung durch enum/defines ...

aber das debuggt sich einfacher.


zudem kann , wenn eh schon malloc/free verwendet wird
ein konstructor - gebilde gebaut werden


geraet_t *netzteil;

netzteil = malloc ( sizeof( geraet_t ));

Licht(netzteil , AN);


oder durch fp* + macros..

netzteil->licht(1);

von Rahul D. (rahul)


Lesenswert?

Vielen Dank für die Rückmeldungen.

Der Aufruf selber ist eigentlich nicht das Problem.
Mich würde interessieren, wie ich die UART und I²C-Schnittstelle 
gleichartig ansprechen kann.
Ich habe schon eine struct, die einen Pointer auf den UART-Treiber 
enthält.
Wenn ich jetzt einen I²C-Treiber über diese struct ansprechen will, 
müsste ich dafür auch einen entsprechenden Pointer einbinden und eine 
Variable zur Unterscheidung der jeweiligen Schnittstellensorte, oder?

(Ich mache hier (im Forum) wohl gerade das Brainstorming, das ich 
eigentlich an meinem Schreibtisch machen sollte... ;) )

von hust (Gast)


Lesenswert?

jeder treiber hat eine struct mit fp* zu den passenden
read/write/init/conf
funktionen

deine funktionen nutzen dann eben nur die struct
1
device->phy->write( param , len );

wobei phy ein struct mit den fp der jeweiligen HW ist
ob das nun I²C oder UART /SPI ist ... das musst du vorher eben festlegen

1
void licht( *device , param ){
2
   device->phy->write( param , len );
3
}

von A. S. (Gast)


Lesenswert?

Eigentlich macht man das, indem man logische Devices (hier 1, mit 3 
Instanzen) und physikalische erstellt und verbindet.
1
struct Lampe; /*logisches Device*/
2
typedef struct sLampe *TLampe; /*Zeiger*/
3
TLampe LKinderzimmer;
4
TLampe LWohnzimmer;
5
TLampe LFlur;
6
7
/*physikalische, im selben Schema*/
8
typedef struct sphyUSB TphyUSB; 
9
typedef struct sphyI2C TphyI2C;
10
typedef struct sphyUart TphyUart;
11
12
TphyUSB phyUSB_a;
13
TphyUSB phyUSB_b;
14
TphyUart phyUart;
15
16
void LichtAn(TLampe L); /*logische Funktion*/

Alternativ zu LichtAn als Funktion kann es auch Memberfunktion sein

die physikalischen Devices enthalten die Umsetzung, mit allgemeinen 
Funktionen oder quasi das phy-Pendant zu LichtAn. sphyUart.LichtAn() 
sendet z.B. "Befehl 37" raus.

Es gibt dann je phy-Typ eine oder zwei Create und Initroutine, in denen 
der Speicher für den Treiber und z.B. Baudrate und Port mitgegeben 
werden (Uart):
phyUSBA_a = CreateUart(usb_a, 9600, ...);

Ebenso für die logischen Devices
LKinderzimmer = CreateLampe(lampe1, ...);

Zusätzlich gibt es je eine Verknüpfungsrouteine, die TLampe mit einem 
physikalischen Device verbindet.

ConnectLampe_USB(TLampe L, TphyUSB USB);
ConnectLampe_I2C(TLampe L, TphyI2C I2C);
ConnectLampe_Uart(TLampe L, TphyUart Uart);

Warum jeweils 3 Routinen? Weil es keine Gemeinsamkeiten gibt zwischen 
USB, I2C und Uart. Es macht 0 Sinn, die Obermenge zu abstrahieren und 
entsprechend kryptische Funktionen und Werteinterpretationen durch den 
Anwendercode zu ziehen.

Man kann manchmal die Initialisierung des physikalischen Devices sparen 
und an die Connect_Funktion dranpappen. Ist aber meist nicht sinnvoll.

Im Code hast Du nacher
1
   LampeAn(LKinderzimmer);
2
   ...
3
   for(p=ListOfLamps, p, p++)
4
   { 
5
       LampeAus(p);
6
   }
bzw. mit Funktionspointern:
1
   LKinderzimmer->LampeAn(LKinderzimmer):
2
   ...
3
   for(p=ListOfLamps, p, p++)
4
   { 
5
       p->LampeAus(p);
6
   }

von Reinhard M. (reinhardm)


Lesenswert?

Moin moin,

Achim S. schrieb:
> Warum jeweils 3 Routinen? Weil es keine Gemeinsamkeiten gibt zwischen
> USB, I2C und Uart.

Naja, sowas kommt raus, wenn man nicht richtig nachdenkt ;)

USB, I2C und Uart sind jeweils Frame-orientierte Übertragungsprotokolle. 
Ich sehe da schon Gemeinsamkeiten. Eine öffentliche Schnittstelle könnte 
so aussehen, dass man eine Adresse und die Daten für den Frame übergibt 
und eine Übertragungs-Funktion ausführt. Im Internen Bereich sind dann 
die Unterschiede versteckt.

Das könnte man folgendermaßen umsetzen:
1
struct USB_Info {
2
  int8_t   (*send)   (void* info, uint8_t address, uint8_t data);
3
  uint8_t  (*receive)(void* info, uint8_t address);
4
5
  // what ever
6
7
  } commUSB;
8
9
struct I2C_Info {
10
  int8_t   (*send)   (void* info, uint8_t address, uint8_t data);
11
  uint8_t  (*receive)(void* info, uint8_t address);
12
13
  // what ever
14
15
  } commI2C;
16
17
18
struct UART_Info {
19
  int8_t   (*send)   (void* info, uint8_t address, uint8_t data);
20
  uint8_t  (*receive)(void* info, uint8_t address);
21
22
  // what ever
23
24
  } commUART;
25
26
27
struct Device {
28
  uint8_t   address;
29
  uint8_t   onCommand;
30
  uint8_t   offCommand;
31
  int8_t   (*send)   (void* info, uint8_t address, uint8_t data);
32
  uint8_t  (*receive)(void* info, uint8_t address);
33
  void*     commInfo;
34
  };
35
36
37
#define LAMPE_KINDERZIMMER  0x37
38
#define LAMPE_WOHNZIMMER    0x22
39
#define LAMPE_FLUR          0x10
40
41
#define USB_EIN             0x80
42
#define USB_AUS             0x0F
43
44
#define I2C_EIN             0x33
45
#define I2C_AUS             0x23
46
47
#define UART_EIN            0xF3
48
#define UART_AUS            0xF9
49
50
#define AUS                 0
51
#define AN                  (!AUS)
52
53
54
Device LampeKinderzimmer = {
55
  .address    = LAMPE_KINDERZIMMER,
56
  .onCommand  = USB_EIN,
57
  .offCommand = USB_AUS,
58
  .send       = commUSB.send,
59
  .receive    = commUSB.receive,
60
  .commInfo   = &commUSB
61
  };
62
Device LampeWohnzimmer = {
63
  .address    = LAMPE_WOHNZIMMER,
64
  .onCommand  = I2C_EIN,
65
  .offCommand = I2C_AUS,
66
  .send       = commI2C.send,
67
  .receive    = commI2C.receive,
68
  .commInfo   = &commI2C
69
  };
70
Device LampeFlur = {
71
  .address    = LAMPE_FLUR,
72
  .onCommand  = UART_EIN,
73
  .offCommand = UART_AUS,
74
  .send       = commUART.send,
75
  .receive    = commUART.receive,
76
  .commInfo   = &commUART
77
  };
78
79
80
81
int8_t schalte(struct Device* device, int8_t on) {
82
  if (on) return (device->send)(device->address, device->onCommand,  device->commInfo);
83
  return         (device->send)(device->address, device->offCommand, device->commInfo);
84
  }
85
86
87
// Aufruf:
88
89
if (!schalte(Lampe_Kinderzimmer, AN)) {
90
   // Lampe wurde geschaltet ...
91
   }
Wer will könnte die Funktion schalte() auch durch ein Makro ersetzen 
(erspart einen Funktionsaufruf und Registersicherungen).

Wenn die Funktionen der Kommunikations-Module öffentlich zugänglich 
sind, könnte man die Funktionszeiger in den commXX-Strukturen sparen 
(Platz) und die Zeiger in den Device-Strukturen direkt setzen.

von W.S. (Gast)


Lesenswert?

Reinhard M. schrieb:
> USB, I2C und Uart sind jeweils Frame-orientierte Übertragungsprotokolle.

Wie bitte?

Du schreibst Unsinn.
Beim UART hat man völlig asynchrone Einzelzeichen-Übertragung
Beim I2C hat man adressierte Einzelzeichen-Übertragung (OpenSlave, 
Übertragung(en), CloseSlave)
Beim USB hat man erstmal garnichts, sondern muß sich aus den 
Möglichkeiten, die USB so bietet, eine aussuchen und implementieren. Ob 
das nun Frame-orientiert ist oder nicht, isochron ist oder nicht und und 
und, hängt davon ab, was man da implementiert.


Rahul D. schrieb:
> Ich würde mir nun gerne eine Art Treiber scheiben (Keil C, RTOS u.a.
> wegen USB-Host), der dafür sorgt, dass das übergeordnete Programm alle
> Geräte gleich ansprechen kann.

Ja, ist verständlich.

> Wie realisiert man das?

Etwa so:
1
#define CSTA  const struct TActuator
2
struct TActuator
3
{ int AllgDaten;   // etc.
4
  void  *Data;     // zeiger auf die veränderlichen Daten
5
  // hier kommen nun die Methoden:
6
  void  (*OnInit)     (CSTA* self, weitere parameter..);
7
  void  (*OnMachwas)  (CSTA* self, weitere parameter..);
8
  void  (*OnSchaltEin)(CSTA* self, weitere parameter..);
9
  void  (*OnSchaltAus)(CSTA* self, weitere parameter..);
10
};

So. So einen Struct kann man in den Flash legen. Der Typ muss 
einheitlich sein für alle Aktuatoren, die du so hast, egal ob so einer 
an einem UART oder I2C oder sonstwo dran hängt.

Die allgemeinen daten im struct sollen allgemeingültig sein, also alles, 
was du so an allgemeinen und konstanten Daten benötigst. Für spezielle 
Daten, die sich auch ändern können, mußt du *Data benutzen. Der ist 
void, du mußt also dort in jedem Falle casten. Aber genau DAS machst du 
niemals in deinen übergeordneten Programmteilen, sondern NUR in deinen 
Treibern, die per OnInit oder OnMachwas oder so aufgerufen werden.

Alle solche Treiber müssen das gleiche Interface haben, also die 
gleichen allgemeinen Parameter im Aufruf haben. Dazu kommt ein 'self', 
der auf den aktuellen struct zeigen muß. Zu jeder Interfaceart schreibst 
du dir nen Treiber. Das Entscheidende ist deren gleiches Interface zum 
aufrufenden Programm hin. Du hast also pro Gerät einen Struct im Flash. 
Dort stehen die nötigen Daten drin (z.B. Adresse oder Formatangaben für 
dessen Ansprechen) und es stehen die Funktionen drin, mit denen man das 
Gerät anspricht. Wenn variable Daten benötigt werden, bekommt jeder 
Struct einen eigenen Bereich im RAM, wo *Data hinzeigt.

W.S.

von Reinhard M. (reinhardm)


Lesenswert?

>> USB, I2C und Uart sind jeweils Frame-orientierte Übertragungsprotokolle.
>
> Wie bitte?
>
> Du schreibst Unsinn.

Ja, war klar :)

Kommt eben drauf an, ob man Gemeinsamkeiten finden will, oder nicht.
Ich hatte den TE so verstanden, dass er nach Gemeinsamkeiten suchte und 
zugleich seinen Code leserlicher machen wollte.

Wenn ich mir die Protokolle aus Anwendersicht anschaue, dann kann ich 
mit jedem mehrere Clients ansprechen. Brauche also irgendetwas, um die 
Clients zu unterscheiden. Üblicherweise nennt man das Adresse

Dann will ich dem Client ja auch was mitteilen, in diesem konkreten Fall 
eben "einschalten". Dafür würde ein Bit ausreichen. Mir ist aber nicht 
bekannt, dass die Protokolle die Übertragung eines einzigen Bittes 
unterstützen würden. Also muss man das Bit bis zur 
Mindestübertragungsgröße auffüllen. Manchmal ist es ein Byte, kann aber 
auch mehr oder weniger sein. Deshalb sprach ich von Frames. Ein Frame 
ist einfach eine feste Anzahl an Bits, die mich als Anwender aber 
garnicht interessieren muss ;)

Wie auch immer, ich würde das, was ich dem Client mitteilen will als 
Nutzdaten (bezogen auf die Übertragung) bezeichnen. Also sind wir 
genau bei dem von mir modellierten Ansatz. Wenn es eine reine 
Master-Slave-Verbindung ist, könnte man den Lese-Kanal sogar weglassen. 
Man könnte auch noch weitere Übertragungsprotokolle wie Ethernet oder 
SPI zufügen ohne an dem Konzept irgendwas ändern zu müssen.

Aber wie gesagt: diese Sichtweise muss man auch wollen :)

Was mir bei Deinem Konzept nicht einleuchtet: wieso soll ich Platz für 
vier Funktionszeiger verschwenden, wenn einer dafür ausreicht?

von W.S. (Gast)


Lesenswert?

Nana, DU bist doch garnicht der TO. De wollte seine diversen 
Aktuatoren trotz höchst unterschiedlicher HW auf gleiche Weise behandeln 
können.

Und wenn dir ein Beispiel namens OnMachwas(...) nicht von selbst 
einleuchtet, dann sei's dir erklärt: Das ist ein Beispiel. Man braucht 
nicht vier Funktionspointer, sondern eben so viele, wie man im konkreten 
Fall eben braucht - genauso geht's mit eventuellen konstanten Daten 
und/oder Variablen. Aber das ist Sache des TO.

Nochwas: Wenn dir schon klar war, daß du Unsinn schreibst, warum tatest 
du es? Frame-orientierte Übertragungsprotokolle sind solche, die eben 
einen Frame, also einen Rahmen haben. Ethernet-Pakete zum Beispiel. Aber 
ein asynchron gesendetes Zeichen auf einer seriellen Schnittstelle 
gehört nicht dazu. Es ist eben nur ein Zeichen und sonst nix.

W.S.

von S. R. (svenska)


Lesenswert?

W.S. schrieb:
> De wollte seine diversen Aktuatoren trotz höchst unterschiedlicher
> HW auf gleiche Weise behandeln können.

Wollte er dies nicht, so bräuchte er kein OOP.

> Man braucht nicht vier Funktionspointer, sondern eben so viele,
> wie man im konkreten Fall eben braucht

Du kannst einen Aktuator als "kann links(), rechts(), oben(), unten() 
und stop() ausführen" betrachten. Du kannst einen Aktuator aber auch als 
"verarbeitet Bytesequenzen nach Protokoll XY" betrachten.

Im ersten Fall ist dein Ansatz sinnvoller.
Im zweiten Fall ist Reinhards Ansatz sinnvoller.
Per se Unsinn ist keiner der Ansätze.

> Frame-orientierte Übertragungsprotokolle sind solche, die eben
> einen Frame, also einen Rahmen haben.

"A frame typically includes frame synchronization features consisting of 
a sequence of bits or symbols that indicate to the receiver the 
beginning and end of the payload data within the stream of symbols or 
bits it receives." (Wikipedia)

In meiner naiven Weltsicht erfüllt die Übertragungseinheit einer 
seriellen Schnittstelle (also Startbit, Datenbits, Paritätsbit und 
Stopbits) diese Anforderungen. Und um Reinhard zu zitieren:
>> Kommt eben drauf an, ob man Gemeinsamkeiten finden will, oder nicht.

Du willst offensichtlich keine Gemeinsamkeiten finden. Deine 
Entscheidung.

> Aber ein asynchron gesendetes Zeichen auf einer seriellen Schnittstelle
> gehört nicht dazu. Es ist eben nur ein Zeichen und sonst nix.

Das Zeichen ist der Payload. Davor und dahinter sind aber auch wichtige 
Bits.

von W.S. (Gast)


Lesenswert?

S. R. schrieb:
> In meiner naiven Weltsicht erfüllt die Übertragungseinheit einer
> seriellen Schnittstelle

Ach, frag dich doch erstmal, ob der TO von solcher Sophisterei einen 
Nutzen hätte. Ich bezweifle dies.

Und hier geht es auch nicht darum, was Reinhard fokussiert:
Reinhard M. schrieb:
> USB, I2C und Uart sind jeweils Frame-orientierte Übertragungsprotokolle.
> Ich sehe da schon Gemeinsamkeiten. Eine öffentliche Schnittstelle könnte
> so aussehen, dass man..

Nein, Eben genau das ist eigentlich egal. Dem TO ging es nicht darum, 
Gemeinsamkeiten diverser Peripheriecores und deren Verhalten zu finden, 
sondern er will seine Aktuatoren (O-Ton: "verschiedene gleichartige 
Geräte") unabhängig von deren Anbindung auf gleiche Weise ansprechen.

Nochmal zum Mitschreiben: Die Geräte sind das Ziel und nicht die Strippe 
zu ihnen.

Deshalb soll es für das Ansprechen herzlich wurscht sein, ob das eine am 
I2C und das nächste am USB-OTG oder sonstwo dran hängt. Ich hab ihm eine 
Art Quasi-Objekt-Lösung vorgeschlagen, wo die Unterschiede in der Art 
der "Strippe" im jeweiligen Objekt gehandhabt werden und das Interface 
zum aufrufenden Programm hardwareunabhängig ist.

Klaro?

W.S.

von Achim.S (Gast)


Lesenswert?

Reinhard M. schrieb:
> Kommt eben drauf an, ob man Gemeinsamkeiten finden will, oder nicht.
> Ich hatte den TE so verstanden, dass er nach Gemeinsamkeiten suchte und
> zugleich seinen Code leserlicher machen wollte.

Das klappt aber bei Dir nicht. Vermutlich hast Du meinen Post entweder 
nicht gelesen oder nicht verstanden, ebenso nicht die Ausführungen von 
W.S.

Bei Deinem Bemühen, Gemeinsamkeiten zu finden, verlagerst Du

A)Implementierungsdetails der Device-Treiber in die abstrakte 
Lampenebene.

Reinhard M. schrieb:
> Device LampeKinderzimmer = {
>   .address    = LAMPE_KINDERZIMMER,
>   .onCommand  = USB_EIN,
>   .offCommand = USB_AUS,
>   .send       = commUSB.send,
>   .receive    = commUSB.receive,
>   .commInfo   = &commUSB
>   };

und legst Dir Beschränkungen bei der Implementierung auf, um künstlich 
"Gemeinsamkeiten" zu beschwören.

W.S.: (oder mein Beispiel) trennt die Logische Ebene (Lampen schalten) 
komplett den Device-Treibern. Falls es Gemeinsamkeiten gibt, spricht 
ja nichts dagegen, die aus einer gemeinsamen Basis zu bedienen, auf 
keinen Fall aber über die Lampenebene.

Wenn Du solche Device-Treiber mal wirklich selbst durchdenkst (von A bis 
O), dann wirst Du das folgende früher oder später selbst erkennen:

Achim S. schrieb:
> Warum jeweils 3 Routinen? Weil es keine Gemeinsamkeiten gibt zwischen
> USB, I2C und Uart. Es macht 0 Sinn, die Obermenge zu abstrahieren und
> entsprechend kryptische Funktionen und Werteinterpretationen durch den
> Anwendercode zu ziehen.

Explizit nochmal zu Deiner Frage:

Reinhard M. schrieb:
> wieso soll ich Platz für
> vier Funktionszeiger verschwenden, wenn einer dafür ausreicht?

Weil die 4 Funktionszeiger in dem Beispiel sehr les- und handhabbar 
sind. Wenn dem Designer 1 Funktionszeiger lieber ist, macht er nur eine 
Funktion "DoCommand(..., int cmd, ...)" mit festgelegten Kommandos. Aber 
genau nicht auf Datenebene der Devicetreiber, sondern abstrakt dazu, 
z.B. 1=Init, 2=An, 3=Aus, 4=Toggle,....)

von Reinhard M. (reinhardm)


Lesenswert?

> Das klappt aber bei Dir nicht.

Nur weil ich es anders als Du mache, heißt es noch lange nicht, dass es 
nicht klappt.

> Bei Deinem Bemühen, Gemeinsamkeiten zu finden, verlagerst Du
> A)Implementierungsdetails der Device-Treiber in die abstrakte
> Lampenebene.

Naja - ich habe die Abstraktion eben anders aufgeteilt. Nicht streng 
religiös nach Schichten, sondern nach Initialisierung und Aufruf.
Der TE schrieb ja, dass sich eine Lampe (ein device) in Adresse und 
Kommunikationskanal unterscheiden kann. Ich habe dann einfach die 
Flexibilität etwas erhöht und angenommen, dass sich auch die Nutzdaten 
je nach Kommunikationskanal ändern könnnten. Deshalb habe ich die 
Variable mit in die Struktur aufgenommen. Wenn diese Flexibilität nicht 
gebraucht wird, kann man die Variable auch einfach wieder entsorgen :D

Bei der Initialisierung der Struktur muss/sollte man wissen, welche 
Kommunikationsart verwendet wird. Aber nur da.

Bei der Verwendung der Strukturen ist es völlig transparent, welche 
Kommunikationsart, -adresse und -befehl verwendet werden. Das ist in der 
Struktur versteckt. Somit können beliebig viele devices 
unterschiedlichster Art über ein und das selbe Array und die jeweils 
gleiche Funktion angesprochen werden.
Das ist jetzt nicht OO nach C++ sondern eben reines C.

Also genau das, was der TE wünschte.

Wenn mein Vorschlag mit irgendeiner Abstraktionsphilosophie nicht 
harmoniert, dann ist mir das völlig schnuppe. Die Lösung funktioniert.
Ob sie den Anforderungen entspricht, kann eigentlich nur der TE 
entscheiden :)

von W.S. (Gast)


Lesenswert?

Reinhard M. schrieb:
> Naja - ich habe die Abstraktion eben anders aufgeteilt.

Nein, du hast es noch immer nicht begriffen. Natürlich kann man immer 
mit dem Argument kommen, daß es ja auch anders ginge. Stimmt, in 
Assembler kann man alles - nur so als Beispiel. Aber das ist hier kein 
wirkliches Argument.

Mal ganz vom hohen Roß aus gesprochen:
Objektorientierte Programmierung besteht darin, daß man Objekte hat, die 
nach außen hin, also in Richtung der übergeordneten Programmschichten, 
sich gleich verhalten, obwohl sie im Inneren höchst unterschiedlich 
sein können. Dazu haben sie alle einen Satz von Methoden und allgemeinen 
Eigenschaften, die auf alle Objekte zutreffen - obwohl sie im Inneren 
sowohl unterschiedliche weitere Eigenschaften haben können als auch im 
Inneren der äußerlich gleichen Methoden die Luzie von einem Objekt zum 
anderen ganz unterschiedlich abgeht.

Lehrbeispiel sind grafische Elemente auf einem Programmfenster: Selbiges 
sagt nur "For All My Elements: Draw_Yourself();" und es ist dem Fenster 
egal, ob das nun ein Punkt, eine Linie, Dreieck, Kreis oder Kringel ist. 
Im Inneren dieser Objekte sind unterschiedliche Zeichenmethoden 
vorhanden, denn ein Kreis zeichnet sich anders als ein Dreieck. Aber 
gegenüber dem Fenster sind sie alle gleich.

Nun klaro?

W.S.

von S. R. (svenska)


Lesenswert?

Konkretes Beispiel: HD44780-Display, angeschlossen per SPI, I2C oder 
GPIO.

Dein Ansatz: Ich schreibe drei Treiber, DP_SPI, DP_I2C, DP_GPIO, die 
jeweils eine Methode PrintString() besitzen.

Mein Ansatz: Ich schreibe drei Treiber, BUS_SPI, BUS_I2C, BUS_GPIO, die 
jeweils eine Read()- und eine Write()-Methode haben, und einen Treiber 
DP, der eine Methode PrintString() hat.

Welcher Ansatz ist immer und ausnahmslos besser?

von Carl D. (jcw2)


Lesenswert?

S. R. schrieb:
> Welcher Ansatz ist immer und ausnahmslos besser?

Das Wichtigste ist eben:

Reinhard M. schrieb:
> Das ist jetzt nicht OO nach C++ sondern eben reines C.

;-)

von Reinhard M. (reinhardm)


Lesenswert?

> Welcher Ansatz ist immer und ausnahmslos besser?

Keine Ahnung. Wen interessierts?

Ich verwende C nur für die kleinen AVRs - und dabei ist mir ein 
Kompromiss zwischen lesbarem Code, Codegröße und 
Ausführungsgeschwindigkeit wichtig.
Mir ist nicht wichtig, dass mir ein Theoretiker über den Kopf streicht 
und meint: haste gut gemacht. Wenn er die Nase rümpft und meint: was 
soll der Chice - tja, vielleicht hat er dann ja nicht verstanden, worauf 
es ankommt?

Für mich zählen die 3 genannten Eckpunkte.

Bei größeren Prozessoren greife ich dann schon zu C++ - da wird dann 
auch anders modelliert. Das kostet aber viel Speicher und Laufzeit 
gegenüber der C-Variante. Also muss ich bereit sein, die Nachteile für 
eine sauberere Kapselung in Kauf zu nehmen.

Sich in C die Nachteile von C++ anzutun, ohne von irgendwelchen 
Vorteilen zu profitieren, ist für mich nicht erstrebenswert ;)

von A. S. (Gast)


Lesenswert?

S. R. schrieb:
> Welcher Ansatz ist immer und ausnahmslos besser?

Natürlich der zweite Ansatz.

Der spräche auf den ersten Blick für Reinhard (mehr im Aufrufer 
zentralisieren). M.E. hinkt der Vergleich hier aber, da Reinhard nicht 
nur Printf (da stimme ich zu), sondern (um im Beispiel zu bleiben) auch 
Display-Kommandos und Schnittstellen-internas zum Aufrufer verlagert 
(das lehne ich ab).

von Jobst Q. (joquis)


Lesenswert?

Reinhard M. schrieb:

> Device LampeKinderzimmer = {
>   .address    = LAMPE_KINDERZIMMER,
>   .onCommand  = USB_EIN,
>   .offCommand = USB_AUS,
>   .send       = commUSB.send,
>   .receive    = commUSB.receive,
>   .commInfo   = &commUSB
>   };

So etwas hat in der Firmware einer Steuerung nichts zu suchen. Das sind 
Konfigurationsdaten, die man am besten im Konfigurationsgerät (PC, 
Smartphone öä) speichert. In die Firmware gehören universelle Geräte, 
die man von außen konfigurieren kann.

Sonst muss man ja für jede Änderung, für jedes neue Gerät, den µC neu 
programmieren. Und wenn der Nachbar oder Schwager auch so etwas 
will,fängt man nochmal von vorne an.

von Reinhard M. (reinhardm)


Lesenswert?

> So etwas hat in der Firmware einer Steuerung nichts zu suchen. Das sind
> Konfigurationsdaten, die man am besten im Konfigurationsgerät (PC,
> Smartphone öä) speichert.

Ok, dann habe ich wohl was falsch verstanden.
Ich bin davon ausgegangen, dass der Master auch ein µC mit Firmware wäre 
und dass es um die Programmierung der Master-Seite geht.

Für die Treiberseite ist das was ich geschrieben habe, natürlich Kwatsch 
mit Source =:O

Das würde ich auf Client-Seite nie so machen. Ich hatte die Clientseite 
auch nie im Blickfeld. Wenn das gefordert war, dann bitte ich alle meine 
Beiträge zu ignorieren ;)

Wenn die Steuerung die Intelligenz hat, sich Konfigurationsdaten von 
außen rein zu ziehen, dann kann man die Feilder .adress, .onCommand und 
.offCommand auch nachträglich überschreiben. Wo ist das Problem?

> und Schnittstellen-internas zum Aufrufer verlagert (das lehne ich ab).

Davon kann keine Rede sein. Bitte meine Beiträge nochmal anschauen.
Es ging nicht darum, Schnittstellen-Internas zum Aufrufer hoch zu 
ziehen, sondern die Flexibilität zu haben, wenn ein Client per USB eine 
andere Befehlssequenz braucht, als per I2S, dass man das dann eben mit 
abbilden kann.
... und zwar so, dass der Aufrufer (also der Benutzer der Struktur) nix 
davon mitbekommen muss.
Von den Schnittstellen-Internas will ich ganz sicher nix zum Aufrufer 
hoch ziehen.

: Bearbeitet durch User
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.