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".
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 ...
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:
> 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
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).
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);
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... ;) )
Eigentlich macht man das, indem man logische Devices (hier 1, mit 3
Instanzen) und physikalische erstellt und verbindet.
1
structLampe;/*logisches Device*/
2
typedefstructsLampe*TLampe;/*Zeiger*/
3
TLampeLKinderzimmer;
4
TLampeLWohnzimmer;
5
TLampeLFlur;
6
7
/*physikalische, im selben Schema*/
8
typedefstructsphyUSBTphyUSB;
9
typedefstructsphyI2CTphyI2C;
10
typedefstructsphyUartTphyUart;
11
12
TphyUSBphyUSB_a;
13
TphyUSBphyUSB_b;
14
TphyUartphyUart;
15
16
voidLichtAn(TLampeL);/*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
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:
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.
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
structTActuator
3
{intAllgDaten;// etc.
4
void*Data;// zeiger auf die veränderlichen Daten
5
// hier kommen nun die Methoden:
6
void(*OnInit)(CSTA*self,weitereparameter..);
7
void(*OnMachwas)(CSTA*self,weitereparameter..);
8
void(*OnSchaltEin)(CSTA*self,weitereparameter..);
9
void(*OnSchaltAus)(CSTA*self,weitereparameter..);
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.
>> 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?
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.
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.
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.
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,....)
> 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 :)
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.
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?
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.
;-)
> 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 ;)
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).
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.
> 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.