Forum: Compiler & IDEs Hardware Abstraktion in C++ (dependency injection?)


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von ARM M4 (stm32) (Gast)


Lesenswert?

Hi,

wie abstrahiert ihr von eurer Mikrocontrollerumgebung wenn ihr 
Chiptreiber schreibt? Z.B. für einen externen SPI ADC.

Ich habe hier 3 Beispiele geschrieben: https://godbolt.org/z/PEKv9a

Variante 1: Hardwareimplementierung durch abstrakte Methoden und 
Vererbung
Variante 2: dependency injection von selbst bereitgestelltem Interface
Variante 3: dependency injection von Hersteller HAL

Variante 3 habe ich bisher benutzt und will davon weg, weil es schlechte 
Abhängigkeiten reinzieht und nur schlecht auf einem PC testbar ist. Der 
Vorteil ist dass man idR. weniger Code schreiben muss.

Variante 3b wäre es, seine eigene HAL zu schreiben und diese zu 
injizieren...

Beitrag #6453407 wurde von einem Moderator gelöscht.
Beitrag #6453417 wurde von einem Moderator gelöscht.
Beitrag #6453419 wurde von einem Moderator gelöscht.
Beitrag #6453434 wurde von einem Moderator gelöscht.
Beitrag #6453435 wurde von einem Moderator gelöscht.
Beitrag #6453436 wurde von einem Moderator gelöscht.
Beitrag #6453437 wurde von einem Moderator gelöscht.
Beitrag #6453439 wurde von einem Moderator gelöscht.
Beitrag #6453440 wurde von einem Moderator gelöscht.
Beitrag #6453457 wurde von einem Moderator gelöscht.
Beitrag #6453467 wurde von einem Moderator gelöscht.
Beitrag #6453474 wurde von einem Moderator gelöscht.
Beitrag #6453475 wurde von einem Moderator gelöscht.
Beitrag #6453476 wurde von einem Moderator gelöscht.
Beitrag #6454272 wurde von einem Moderator gelöscht.
Beitrag #6454273 wurde von einem Moderator gelöscht.
Beitrag #6454274 wurde von einem Moderator gelöscht.
Beitrag #6454475 wurde von einem Moderator gelöscht.
Beitrag #6454477 wurde von einem Moderator gelöscht.
Beitrag #6454478 wurde von einem Moderator gelöscht.
von Jan K. (jan_k)


Lesenswert?

Finde die Frage sehr gut und wichtig, keine Ahnung was hier abgeht.

Tatsächlich überlege ich im Moment, Variante 3 nachzubauen, aber in C, 
dann eben mit Funktionszeigern.
Denn: wir kommen mit dem Schreiben unseres HAL nicht mehr hinterher. Und 
auch, wenn es "nur" Fingerübungen sind, so baut man dort Bugs ein und 
muss alles testen.

Ideal wäre eigentlich ein für ISO26262 o.ä. zertifzierter HAL vom 
Hersteller, der vernünftig von der eigentlichen Business Logik, also dem 
wertschaffenden Teil (IP) getrennt wird.

Die Frage ist: Wie macht man das sinnvoll? Entweder per Injection, da 
kenne ich in C aber im Prinzip nur function pointer oder mit einer Makro 
Orgie, auch nicht besonders sexy.

Kennt jemand guten Embedded Code für die genannte Controller Klasse 
(CM3/CM4) in C oder C++, der einen Austausch des HAL möglich macht?

Mir ist schon klar, dass ein HAL eigentlich den Austausch der Hardware 
ermöglichen soll, aber wenn man fest gegen das API des HAL programmiert, 
bleibt man am Ende ja doch bei dem HAL des selben Herstellers hängen.

von W.S. (Gast)


Lesenswert?

Jan K. schrieb:
> Mir ist schon klar, dass ein HAL eigentlich den Austausch der Hardware
> ermöglichen soll, aber wenn man fest gegen das API des HAL programmiert,
> bleibt man am Ende ja doch bei dem HAL des selben Herstellers hängen.

Genau DAS ist ja auch vom Hersteller bezweckt. Aber wenn man sich sein 
eigenes Portfolio an Treiberinterfaces erarbeitet hat, dann ist man von 
so etwas unabhängig.

Das geht aber nicht für alles, denn Spezielles, was der eine µC bietet 
hat der andere eben nicht und wenn man genau das verwendet, dann ist an 
dieser Stelle keine Portabiität gegeben.

W.S.

von Jan K. (jan_k)


Lesenswert?

W.S. schrieb:
> Genau DAS ist ja auch vom Hersteller bezweckt.

Verstehe ich.

W.S. schrieb:
> Aber wenn man sich sein
> eigenes Portfolio an Treiberinterfaces erarbeitet hat, dann ist man von
> so etwas unabhängig.

Verstehe ich auch, mein Chef aber nur begrenzt. Hab ich ja oben schon 
versucht anzudeuten, wir kommen nicht hinterher. Ein anderer Kollege 
(anderes Team, Einzelkämpfer) kommt jetzt mit CubeMX an und freut sich, 
wie schnell er eine funktionierende Lösung zusammengebaut hat und nur 
noch Integrationstest machen muss.

Ideal wäre doch beides - ein leichtgewichtiger (wie auch immer man das 
konkret umsetzt) Abstraktionslayer, der wirklich universell ist und 
gegen den in der Business Logik programmiert wird.

In etwa sowas, wie die CMSIS-Driver/CMSIS-RTOS2 von ARM/Keil. Nur in 
besser :D

Die große Frage ist dann nur: Wie baut man eben nicht-universelle 
Sonderfunktionen ein, die ja häufig auch Hardware features nutzen.

von Ben S. (bensch123)


Lesenswert?

Ich schreibe auf ARM inzwischen nur noch in C++ und generischen Hardware 
Klassen. Beim Portieren brauche ich diese dann nur noch im Quellcode 
anzupassen, sonst nichts. Das Ziel ist es, sämtliche Hardwarezugriffe 
aus dem eigentichen Programm fernzuhalten und durch sprechende Aufrufe 
zu ersetzen.

Wird beispielweise eine USART verwendet, dann wird davon ein Objekt 
erzeugt:
1
 
2
USART oUSART(USART2, 9600);
3
oUSART.send(0xff);

Was dahinter passiert interessiert mich ab diesem Punkt nicht mehr. Es 
ist aber selber in C++ geschrieben, quasi meine eigenen generische HAL. 
Muss ich das portieren, versuche ich die Methoden und deren Parameter 
nicht zu verändern.

In diesen Klassen nutze ich nur CMSIS mit direkten Registerzugriff. HAL 
und sogar LL nutze ich nicht.

W.S. schrieb:
> Genau DAS ist ja auch vom Hersteller bezweckt. Aber wenn man sich sein
> eigenes Portfolio an Treiberinterfaces erarbeitet hat, dann ist man von
> so etwas unabhängig.

Gerade bei den STM32 ist ein Wechsel doch so einfach und macht kaum 
Arbeit. Beispielsweise ist fast durch die gesamte Reihe weg bis auf 
wenige Ausnahmen der Zugriff auf die GPIOs gleich.

: Bearbeitet durch User
von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

@BenS
Jetzt noch mit Interfaces arbeiten und die Klassen die einen UART nutzen 
bekommen eine Referenz darauf.
UartSTM32 implementiert dann das Interface.
Schon ist die Anwendung zwischen den µC austauschbar.
Bzw selbst in der STM32 Serie gibts ja schhon verschiedenene Uarts.

Grobes Beispiel:
1
class IUart {
2
 virtual Write(const uint8_t *buf, uint32_t len) = 0;
3
};
4
5
class Stm32Uart32Bit : public IUart {
6
 virtual Write(const uint8_t *buf, uint32_t len);
7
};
8
9
in den Variablen mits nen static instance is:
10
Stm32Uart32Bit uart2;
11
uart2.Init(UART2, 9600); // UART2 ist der Pointer der UART2 Register aus dem CMSIS
12
13
classNeedsUart kekse;
14
kekse.Init(uart2);

Warum man das jetzt mit Injections machen muss leuchtet mir nicht ein.
Welche Argumente gibts denn dafür?

Jan K. schrieb:
> Finde die Frage sehr gut und wichtig, keine Ahnung was hier abgeht.
moby geht ab und spamt das Forum voll wie geil er asm findet. Da er 
Hausverbot hat sind die posts ganz schnell wieder weg.

: Bearbeitet durch User
von Al Fine (Gast)


Lesenswert?

Mit vituellen Methoden würde ich auf uC aufpassen, weil die doch einiges 
an Overhead erzeugen. Da sind templates/compile-time Polymorphie ggf. 
besser geeignet.
At run-time Implementierungen gegeneinander austauschen braucht man da 
sowieso nicht.

von Ben S. (bensch123)


Lesenswert?

Mw E. schrieb:
> Jetzt noch mit Interfaces arbeiten und die Klassen die einen UART nutzen
> bekommen eine Referenz darauf.

Referenz mache ich natürlich, Referenzen sind Pointern ebenso in nahezu 
allen Belangen vorzuziehen. Interfaces nutze ich nicht, da ich zu selten 
portiere und den Kram dann eh kaum anpassen muss - also kopieren, 
Klassennamen umbenennen, Quellcode ändern, fertig und passt scho. Wer 
jedoch viel portieren muss (z.B. beruflich), für den kann das ganz 
praktisch sein.

von Vincent H. (vinci)


Lesenswert?

Ich finde dass die Frage nicht so generell zu beantworten ist sondern 
von der Art des Produkts, der produzierten Stückzahl und vor allem der 
Langlebigkeit abhängt.

Für eine 10-Stück Kleinserie wo sich die Hardware wohl nie wieder ändert 
ist es schwieriger den Aufwand einer eigenen HAL zu rechtfertigen als 
für eine 200k-Serie die verschiedene Sub-Typen bekommt, eventuell 
verschiedene Prozessoren nutzen muss und die nächsten 15 Jahre lebt.

Dependency Injection nutze ich für Hardware-Bibliotheken eigentlich 
nicht. Ich sehe Hardware eher als was globales an und nutze einen 
Config-Header mit using declarations 
(https://en.cppreference.com/w/cpp/language/using_declaration) um meine 
Peripherie-Klassen der Applikation zuzuordnen.

Z.b.
1
namespace memory {
2
3
using EepromI2c = mylib::I2c<3>;
4
5
}

Und irgendwo im Code dann statische Funktion der peripherie Klasse:
1
void Eeprom::read(...) {
2
  EepromI2c::read(...);
3
}

von Ben S. (bensch123)


Lesenswert?

Al Fine schrieb:
> Mit vituellen Methoden würde ich auf uC aufpassen, weil die doch einiges
> an Overhead erzeugen. Da sind templates/compile-time Polymorphie ggf.
> besser geeignet.

Da stecke ich jetzt nicht tief genug in der Materie. Aber sollte der 
Compiler nicht erkennen, dass ich da einfach eine dynamische Klasse 
erstelle? Warum sollte der beim USART Beispiel Overhead erzeugen und 
welchen?

Auf mikrocontrollern, wo bei meinen bisherigen Projekten alles statisch 
war, arbeite ich mit Dependency Injection. Da ändert sich ja zur 
Laufzeit nie etwas. Erst ein USART Objekt erstellen und anschließend 
jedes Objekt, das eine USART benötigt, wird initialisiert mit einer 
Referenz ein auf ein (dieses) USART Objekt. Ein Initialisieren ohne 
Referenz ist nicht möglich.

Das ist aber mein Weg, den ich gut und klar finde.

: Bearbeitet durch User
von Johannes M. (johannesm)


Lesenswert?

Ihr könntet euch mal im Bereich Autosar umschauen, wie das da machen, 
dort nennen die das MCAL (Microcontroller Abstraction Layer).
Die Spezifikationen sind alle frei zugänglich. Vielleicht lässt sich da 
etwas abschauen. ISO26262 wird dort sicherlich auch behandelt.
Bin leider noch nicht dazu gekommen, in das Thema etwas tiefer 
einzusteigen.

von A. S. (achs)


Lesenswert?

Ben S. schrieb:
> Wird beispielweise eine USART verwendet, dann wird davon ein Objekt
> erzeugt:
> USART oUSART(USART2, 9600);
> oUSART.send(0xff);

Meine Erfahrung ist, dass diese Schnittstelle nicht die Hardware 
abstrahiert, sondern eine logische Funktion (Bytes rauspusten, 
empfangen). Hier passt man eher den HW-nahen Code an die Applikation an.

Mag sich kleinkarriert anhören, ist aber ein komplett anderes Paradigma.

Der Applikationsprogrammierer gibt dabei vor, was er braucht, und der 
HW-nahe implementiert das irgendwie (und nur das).

Die Möglichkeiten und Verwendungen eines uarts sind weit zahlreicher als 
die konkreten Verwendungen in einer Firma über (fast) alle Projekte

: Bearbeitet durch User
von Ben S. (bensch123)


Lesenswert?

A. S. schrieb:
> Meine Erfahrung ist, dass diese Schnittstelle nicht die Hardware
> abstrahiert, sondern eine logische Funktion (Bytes rauspusten,
> empfangen). Hier passt man eher den HW-nahen Code an die Applikation an.
>
> Mag sich kleinkarriert anhören, ist aber ein komplett anderes Paradigma.

Das meinte ich vermutlich auch eher. Ich decke natürlich nicht alles ab 
was eine USART kann, sondern entwerfe benötigte Funktionalitäten frei 
nach meiner Definition. Die einfachste bei einer USART wäre, 8bit 
rauszuschupsen. Das heißt dann bei mir aber projektübergreifend
1
void USART::send(uint8_t u8Val);

Ich glaube ich habe den Begriff dependency injection falsch verstanden.

: Bearbeitet durch User
von Al Fine (Gast)


Lesenswert?

Ben S. schrieb:
> Aber sollte der
> Compiler nicht erkennen, dass ich da einfach eine dynamische Klasse
> erstelle? Warum sollte der beim USART Beispiel Overhead erzeugen und
> welchen?

Naja, was der Compiler wann rausoptimieren kann und was nicht ist immer 
ein wenig Glückssache.
Jedenfalls ist ein virtueller Funktionsaufruf im Allgemeinen (ohne 
Rausoptimiert zu sein) ein Aufruf eines Funktionspointers, der in einer 
Tabelle nachgeschlagen wird, die sich aus dem this-pointer ergibt.
Das sind so grob mindestens 3-4 Instructions pro Aufruf einer virtuellen 
Funktion. Außerdem muss immer der this-Pointer mitgeschleift werden.
Ob das relevant ist, hängt sicher vom use-case ab. Auf einem AtTiny war 
mir jedenfalls mal ziemlich schnell der Programmspeicher vollgelaufen.
Das mit dem globalen Objekt spart aber sicher einiges gegenüber einem 
naiven Aufruf.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Al Fine schrieb:
> Mit vituellen Methoden würde ich auf uC aufpassen, weil die doch einiges
> an Overhead erzeugen.

Das ist am Ende wie ein Aufruf eines Funktionspointer, das bekommen die 
heutigen Compiler schon ganz gut hin.

Ben S. schrieb:
> Interfaces nutze ich nicht, da ich zu selten
> portiere und den Kram dann eh kaum anpassen muss - also kopieren,
> Klassennamen umbenennen, Quellcode ändern, fertig und passt scho. Wer
> jedoch viel portieren muss (z.B. beruflich), für den kann das ganz
> praktisch sein.

Is ja auch beruflich, aber das ging so gut auf, dass ich das privat 
jetzt auch so mache.

von Oliver S. (oliverso)


Lesenswert?

Al Fine schrieb:
> Naja, was der Compiler wann rausoptimieren kann und was nicht ist immer
> ein wenig Glückssache.

Aktuelle Compiler sind schon ziemlich gut darin, genau diesen Fall zu 
erkennen und zu optimieren.

Al Fine schrieb:
> Ob das relevant ist, hängt sicher vom use-case ab. Auf einem AtTiny war
> mir jedenfalls mal ziemlich schnell der Programmspeicher vollgelaufen.

Ohne RTFM sollte man halt nicht unterwegs sein. RTFM hätte in dem Fall 
darauf hingewiesen, daß g++, dynamische Polymorphie und AVRs keine gute 
Kombination sind.

Oliver

von Experte (Gast)


Lesenswert?

ARM M4 (stm32) schrieb im Beitrag #6453402:
> wie abstrahiert ihr von eurer Mikrocontrollerumgebung wenn ihr
> Chiptreiber schreibt? Z.B. für einen externen SPI ADC.
>
> Ich habe hier 3 Beispiele geschrieben:

Keins der drei Beispiele löst das Problem. Sie unterscheiden sich nicht 
in ihrem Abstraktionsniveau, sondern nur in Implementierungsdetails.

Jan K. schrieb:
> Ideal wäre doch beides - ein leichtgewichtiger (wie auch immer man das
> konkret umsetzt) Abstraktionslayer, der wirklich universell ist und
> gegen den in der Business Logik programmiert wird.

Vollkommen falsch, total falsch.

A. S. schrieb:
> Meine Erfahrung ist, dass diese Schnittstelle nicht die Hardware
> abstrahiert, sondern eine logische Funktion (Bytes rauspusten,
> empfangen). Hier passt man eher den HW-nahen Code an die Applikation an.

Genau so wird es gemacht. So und nicht anders.

> Mag sich kleinkarriert anhören, ist aber ein komplett anderes Paradigma.

Nein, es ist nicht kleinkariert, sondern inzwischen ein seit Jahrzehnten 
bekanntes Grundlagenprinzip:

   Abhängigkeiten immer in Richtung der Abstraktion!

Das heißt: Die Business-Logik gibt die Schnittstelle vor. Dagegen wird 
die Hardware programmiert, und nicht umgekehrt.

Beispiel: Wertet die Applikation einen Temperatur-Sensor aus, definiert 
die Applikation eine Schnittstelle dafür. Im einfachsten Falle z.B.:
1
   extern int16_t readTemperatureCentiCelsius();

Das Ding kann natürlich auch in einer virtuellen Klasse, mit tausend 
anderen Schnittstellen definiert sein, als Singleton, als Dependency 
Injection, oder sonst irgendeinem Mechanismus bzw. 
Implementierungsdetail. Für ein komplexe Schnittstelle einer fetten 
Anwendung können das auch unzählige Klassen sein. Piep egal.

Der Punkt ist: Gegen diese Schnittstelle der Business-Logik wird die 
Hardware programmiert. Ob nun die Temperatur über einen ADC am SPI-Bus, 
per I2C, oder per RS485-Busprotokoll von irgendwo her kommt, spielt für 
die Business-Logik keine Rolle.

von A. S. (achs)


Lesenswert?

Experte schrieb:
> Nein, es ist nicht kleinkariert, sondern inzwischen ein seit Jahrzehnten
> bekanntes Grundlagenprinzip:
>
>    Abhängigkeiten immer in Richtung der Abstraktion!
>
> Das heißt: Die Business-Logik gibt die Schnittstelle vor. Dagegen wird
> die Hardware programmiert, und nicht umgekehrt.

Whow. Für mich war das eher die Quintessenz aus mehreren 
Jahren/Projekten/Portierungen Erfahrung und für meine Kollegen 
(API-Götter) eher eine Kröte, die sie "gegen besseres Wissen" schlucken 
mussten.

Hast Du eine Quelle oder ein Schlagwort dafür?

(Mit dem blinden Huhn und so: Am Anfang meiner Laufbahn hat sich mit 
meinem (erfahreneren) Kollegen eine Arbeitsweis ergeben, die kurze Zeit 
später als "Pair Programing" hip wurde)

von Peter D. (peda)


Lesenswert?

ARM M4 (stm32) schrieb im Beitrag #6453402:
> Z.B. für einen externen SPI ADC.

ADC ist ein recht komplexes Thema, den kann man nicht so einfach dem SPI 
übergeben und gut is. Jeder Typ kocht da sein eigenes Süppchen und man 
muß ein ganz bestimmtes Timing einhalten. Manche haben einen separaten 
Busy-Ausgang, manche einen Sync-Eingang, bei manchen geht nur, die 
Wandlungszeit abzuwarten usw. Oft sind auch weitere Wartezeiten 
notwendig, wenn Mux, Gain oder Referenz umgeschaltet werden.
Und oft sind auch die Datenformate recht krude und man muß die Kommandos 
und das Ergebnis hin- und herschieben.

Es ist daher sinnvoll, sich nur auf einen oder wenige ADC-Typen 
festzulegen und dann dafür den bzw. die Treiber zu schreiben.

: Bearbeitet durch User
von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

@peda:
Dafür gibts den hier schon angesprochenen MCAL.
Dieser abstrahiert den SPI.
Darauf baut dann ein DAL auf (Device Abstraction Layer).
Der nutzt dann zB einen Timer fürs Warten, den IRQ/NVIC für Änderungen 
am Busyflag (wenns am Portpin IRQ hängt).
Dazu vllt. noch einen DMA für schnelle ADC wo man viele Werte braucht.
(auch alles per MCAL bereitgestellt)
Der DAL dieses ADC stellt dann die Einlesefunktion bereit und 
Einstellfunktionen für den genannten Gain.
Unter der Haube kann der ja vllt schon die ganze Zeit den letzten Wert 
einlesen (IRQ getrieben Statemachine mit Handlerfunktion in der 
Mainloop, oder mit RTOS und Task) und die Get Funktion ließt den letzten 
zwischengespeicherten.

Die MCAL Teile reicht man dann dem DAL über hier vorgeschlagene 
Methodiken:
dependency injection
interfaces
using declarations

von Peter D. (peda)


Lesenswert?

Mw E. schrieb:
> Dafür gibts den hier schon angesprochenen MCAL.
> Dieser abstrahiert den SPI.

Ich benutze für das SPI einfach eine Inline-Funktion.
Die 8 Takte für ein Byte per Polling abzuwarten, geht erheblich 
schneller, als erstmal das ganze riesen Getöse für einen Kontextwechsel 
anwerfen zu müssen.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Grade eben haste aber noch von einem viel größerem Aufwand für einen ADC 
gesprochen ;)
Wenns nen ADC ist der das nicht braucht, dann sind DAL+MCAL 
dementsprechend "leer".

von Walter T. (nicolas)


Lesenswert?

Wieso eigentlich "Dependency Injection"? Für mich wäre eine 
Hardware-Abstraktion ein typisches Fassade-Pattern?

von Johannes S. (jojos)


Lesenswert?

ich benutze Variante 3, aber mit einem OO OS und damit nicht die direkte 
Abhängigkeit einer HAL.
In den Konstruktor des SPIADC dann eine Referenz auf das SPI Object das 
benutzt werden soll. Man könnte das SPI Objekt im SPIADC erzeugen und 
nur die Pinnames übergeben, aber gerade SPI kann ja von mehreren devices 
verwendet werden und da ist es sinnvoll das SPI Objekt ausserhalb zu 
instanziieren.
Mit virtuellem read/write können dann auch abgeleitete SPI Objekte 
verwendet werden, um z.B. ein Software SPI mit dem SPIADC verwenden zu 
können.

: Bearbeitet durch User
von Jan K. (jan_k)


Lesenswert?

Experte schrieb:
> Jan K. schrieb:
>> Ideal wäre doch beides - ein leichtgewichtiger (wie auch immer man das
>> konkret umsetzt) Abstraktionslayer, der wirklich universell ist und
>> gegen den in der Business Logik programmiert wird.
>
> Vollkommen falsch, total falsch.

Interessante These, aber viel zum Diskutieren, find ich gut.

>
> A. S. schrieb:
>> Meine Erfahrung ist, dass diese Schnittstelle nicht die Hardware
>> abstrahiert, sondern eine logische Funktion (Bytes rauspusten,
>> empfangen). Hier passt man eher den HW-nahen Code an die Applikation an.
>
> Genau so wird es gemacht. So und nicht anders.
>
>> Mag sich kleinkarriert anhören, ist aber ein komplett anderes Paradigma.
>
> Nein, es ist nicht kleinkariert, sondern inzwischen ein seit Jahrzehnten
> bekanntes Grundlagenprinzip:
>
>    Abhängigkeiten immer in Richtung der Abstraktion!
>

Hast du da mal Literatur zu?

> Das heißt: Die Business-Logik gibt die Schnittstelle vor. Dagegen wird
> die Hardware programmiert, und nicht umgekehrt.
>
> Beispiel: Wertet die Applikation einen Temperatur-Sensor aus, definiert
> die Applikation eine Schnittstelle dafür. Im einfachsten Falle z.B.:
>
>
1
>    extern int16_t readTemperatureCentiCelsius();
2
>
>
> ...
>
> Der Punkt ist: Gegen diese Schnittstelle der Business-Logik wird die
> Hardware programmiert. Ob nun die Temperatur über einen ADC am SPI-Bus,
> per I2C, oder per RS485-Busprotokoll von irgendwo her kommt, spielt für
> die Business-Logik keine Rolle.

Ich habe das Gefühl, dass wir hier von zwei verschiedenen Ebenen reden. 
Möglicherweise habe ich auch den Begriff Business Logik im falschen 
Kontext verwendet.

Mir ist schon klar, dass z.B. ein High Level Kommunikationsmodul, das 
sich über UART/CAN/whatever unterhält nicht die eigentliche physische 
Schnittstelle kennt. Oder dass, wenn jemand von außen deine Temperatur 
vom x-beliebigen Tempsensor haben möchte, das Comm-Modul nicht am SPI 
rumfummelt.

Natürlich gibt es dann ein eigenständiges Modul/Klasse/compilation 
unit/..., "TemperaturSensor", das deine Funktion int16_t 
readTemperatureCentiCelsius(); hat.
Natürlich kapselt das Modul nach außen hin, wie es implementiert ist und 
welche Schnittstellen es benutzt. Fakt ist aber doch, dass irgendwelche 
Hardware Schnittstellen benutzt werden.

In deinem Beispiel würdest du dann für jeden neuen µC und vor allem 
jedes angepasste Layout (z.B. mit unterschiedlichem Routing, anderen SPI 
Pinnen, anderer Clock config) das gesamte TemperaturSensor Modul (und 
noch mehr) neu schreiben?!

Warum hier nicht die Hardware Schnittstelle, die eben gegen ein 
Interface programmiert wurde irgendwie übergeben. Dann ist es nämlich 
auch wieder egal, ob es eine SPI oder ein I2C ist, denn die öffentlichen 
Funktionen heißen bei beiden gleich, nämlich
1
uint8_t readData()
 und
1
void writeData(uint8_t data)
 oder so? Und da würde man dann eben den HAL ansetzen.

Habe ich dich falsch verstanden, oder sind wir auf zwei 
unterschiedlichen Ebenen unterwegs?

Danke :)
Jan

von A. S. (achs)


Lesenswert?

Jan K. schrieb:
> Dann ist es nämlich auch wieder egal, ob es eine SPI oder ein I2C ist,
> denn die öffentlichen Funktionen heißen bei beiden gleich, nämlich
> uint8_t readData() und void writeData(uint8_t data) oder so? Und da
> würde man dann eben den HAL ansetzen.

Ja, das klingt gut. Funktioniert nur in der Praxis nicht bzw. nur im 
eigenen Saft. Wenn die HW aus einem Device besteht, nur eine mainloop 
(Task) verwendet wird, Rechenleistung keine Rolle spielt.

In der Praxis stehst Du direkt vor der Frage: blockierend, pollend, 
asynchron, DMA? Und dann schreibst Du 4 Versionen vom Treiber und 
überlegst Dir im Hauptprogramm (High Level), ob bei der aktuellen 
Taktrate von CPU und SPI die blockierende Version angemessen ist.

Jan K. schrieb:
> Hast du da mal Literatur zu?

DIP: Dependency Inversion Principle.

Das D in SOLID

von Peter D. (peda)


Lesenswert?

A. S. schrieb:
> In der Praxis stehst Du direkt vor der Frage: blockierend, pollend,
> asynchron, DMA?

ADC ist oft ein schwieriges Thema, da kann man selten eine Lesefunktion 
schreiben und vergessen. Man muß immer die Seiteneffekte beachten.
Z.B. man hat einen integrierenden ADC mit 10Messungen/s. Dann muß man 
die Lesefunktion in mehrere Teile aufteilen: Starten der Messung, Warten 
auf Ready, Lesen des Ergebnisses.
Oft ist auch die interne Statemaschine des ADC freilaufend, d.h. das 
Starten erfolgt irgendwann, nachdem der gerade laufende interne Zyklus 
fertig ist. Das Ergebnis ist also irgendwann nach 100%..199,9% der 
Meßzeit gültig.

Ich hatte mal nen ADC, bei dem während einer Messung kein Status lesen 
möglich war. Die einzige Möglichkeit, das Ende einer Messung zu erkennen 
war daher, den ADC immer wieder auszulesen, bis sich 2 
aufeinanderfolgende Werte unterschieden haben. Diese Testplatine wurde 
verschrottet und ein anderer ADC genommen.

Beitrag #6460557 wurde von einem Moderator gelöscht.
Beitrag #6460840 wurde von einem Moderator gelöscht.
Beitrag #6460850 wurde von einem Moderator gelöscht.
Beitrag #6460852 wurde von einem Moderator gelöscht.
Beitrag #6460856 wurde von einem Moderator gelöscht.
Beitrag #6460857 wurde von einem Moderator gelöscht.
Beitrag #6496996 wurde von einem Moderator gelöscht.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.