Forum: Mikrocontroller und Digitale Elektronik C: Viele Funktionen zur Auswahl


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

ihr kennt das sicher auch. Man hat plötzlich viele Funktionen, die 
praktisch das gleiche machen.

Fangen wir die Geschichte mit einem LCD an. Die Adafruit-Library gefällt 
mir nicht, also schreibe ich mir meine eigenen Ansteuerungs-Funktionen. 
Auf der untersten Ebene ist das:
1
/* tft_ili9xxx_8bit.c */
2
3
/* Hardware-Initialisierung */
4
void tft_ili9xxx_8bit_initIo(void);
5
6
/* Beliebige Daten senden */
7
exit_t tft_ili9xxx_8bit_put(uint8_t cmd, const uint8_t data[], size_t len); 
8
9
/* Wiederholt die gleichen zwei Bytes senden */
10
exit_t tft_ili9xxx_8bit_putRpt(uint8_t cmd, uint16_t data, size_t nRpt);
11
12
/* 1-Bit-Bitmap schreiben ohne Puffer */
13
exit_t tft_ili9xxx_8bit_putBitmap(uint8_t cmd, const uint8_t *data, size_t nBit, uint16_t val0, uint16_t val1);
14
15
/* Auslesen */
16
exit_t tft_ili9xxx_8bit_get(uint8_t cmd, uint8_t data[], size_t len);
17
18
/* Zuruecksetzen */
19
exit_t tft_ili9xxx_8bit_reset(void);
Die Displays sind gut, aber die 8-Bit-Ansteuerung nervt. Aber endlich 
kommt auch die SPI-Variante mit der Post. Also entsteht die SPI-Variante 
(als Bitbanging-Version, weil gerade kein Hardware-SPI frei ist):
1
/* tft_ili9xxx_spibb.c */
2
void tft_ili9xxx_spibb_initIo(void);
3
exit_t tft_ili9xxx_spibb_put(uint8_t cmd, const uint8_t data[], size_t len); 
4
exit_t tft_ili9xxx_spibb_putRpt(uint8_t cmd, uint16_t data, size_t nRpt);
5
exit_t tft_ili9xxx_spibb_putBitmap(uint8_t cmd, const uint8_t *data, size_t nBit, uint16_t val0, uint16_t val1);
6
exit_t tft_ili9xxx_spibb_get(uint8_t cmd, uint8_t data[], size_t len);
7
exit_t tft_ili9xxx_spibb_reset(void);
Das langsame Bitbanging nervt, also werden auf dem Eval-Board Drähte 
durchgetauscht, bis das Display an einem Hardware-SPI seinen Platz 
findet. Folge sind wieder sieben neue Funktionen:
1
/* tft_ili9xxx_spidma.c */
2
void tft_ili9xxx_spidma_initIo(void);
3
exit_t tft_ili9xxx_spidma_put(uint8_t cmd, const uint8_t data[], size_t len); 
4
exit_t tft_ili9xxx_spidma_putRpt(uint8_t cmd, uint16_t data, size_t nRpt);
5
exit_t tft_ili9xxx_spidma_putBitmap(uint8_t cmd, const uint8_t *data, size_t nBit, uint16_t val0, uint16_t val1);
6
exit_t tft_ili9xxx_spidma_get(uint8_t cmd, uint8_t data[], size_t len);
7
exit_t tft_ili9xxx_spidma_reset(void);
Und natürlich hat man auch schon eine Emulation mit SDL2 für den PC 
geschrieben:
1
/* tft_sdl_mockup.c */
2
void tft_sdl_initIo(void);
3
exit_t tft_sdl_put(uint8_t cmd, const uint8_t data[], size_t len); 
4
exit_t tft_sdl_putRpt(uint8_t cmd, uint16_t data, size_t nRpt);
5
exit_t tft_sdl_putBitmap(uint8_t cmd, const uint8_t *data, size_t nBit, uint16_t val0, uint16_t val1);
6
exit_t tft_sdl_get(uint8_t cmd, uint8_t data[], size_t len);
7
exit_t tft_sdl_reset(void);
Das sind ganz schön viele Funktionen, die das Gleiche machen - und ich 
benötige jeweils nur eine. Wie gehe ich weiter vor?

*A: "Linker for the win"*

Die Funktionen mit gleicher Funktionalität für unterschiedliche 
Ziel-Hardware heißen gar nicht unterschiedlich, sondern gleich. Damit 
liegt es am Linker, jeweils für das richtige Target die richtige Datei 
zu linken.

Dass irgendwer einmal den Quelltextdateihaufen in einer anderen 
Entwicklungsumgebung bauen will, ist unwahrscheinlich oder interessiert 
nicht.

Suchfunktionen in der Entwicklungsumgebung werden auch eher nicht 
genutzt.


*B: "Preprocessor - this is some serious empty translation unit"*

Wie in A heißen die Funktionen gar nicht unterschiedlich, sondern 
gleich. In den Dateien wird eine Konfigurationsdatei "included" und 
Präprozessor-Makro-Vergleiche klammern die ungewünschten Code-Teile aus.


*C: "Der Präprozessor trägt die frohe Kunde hinaus"*

In einer Header-Datei werden die Zeichenketten in den aufrufenden 
Funktionen umbenannt:
1
/* tft.h */
2
#if defined( TFT_ILI9XXX_SPI_DMA )
3
    #define tft_initIo     tft_ili9xxx_spidma_initIo
4
    #define tft_put        tft_ili9xxx_spidma_put
5
    #define tft_putRpt     tft_ili9xxx_spidma_putRpt
6
    #define tft_putBitmap  tft_ili9xxx_spidma_putBitmap
7
    #define tft_get        tft_ili9xxx_spidma_get
8
    #define tft_reset      tft_ili9xxx_spidma_reset
9
#elif defined( TFT_ILI9XXX_SPI_BB )
10
    /* [...Wiederholung fuer jede Variante...] */
Mit Präprozessor-Makros kann dafür gesorgt werden, dass als 
Quelltext-Dateien auch für alle Targets kompilieren und zumindest eine 
anspringbare Dummy-Funktion liefern. Das trifft auch auf die folgenden 
Varianten zu.


*D: "Nicht einmal der Präprozessor weiss mehr, wie Du heißt"*

In einer Header-Datei werden die Zeichenketten der aufzurufenden 
Funktionen umbenannt:
1
/* tft.h */
2
#if defined( TFT_ILI9XXX_SPI_DMA )
3
    #define tft_ili9xxx_spidma_initIo     tft_initIo   
4
    #define tft_ili9xxx_spidma_put        tft_put      
5
    #define tft_ili9xxx_spidma_putRpt     tft_putRpt   
6
    #define tft_ili9xxx_spidma_putBitmap  tft_putBitmap
7
    #define tft_ili9xxx_spidma_get        tft_get      
8
    #define tft_ili9xxx_spidma_reset      tft_reset    
9
#elif defined( TFT_ILI9XXX_SPI_BB )
10
    /* [...Wiederholung fuer jede Variante...] */


*E: "The Gangsta Wrapper"*

In einer Header-Datei werden die aufzurufenden Funktionen gewrappt:
1
/* tft.h */
2
#if defined( TFT_ILI9XXX_SPI_DMA )
3
4
static inline void tft_initIo(void)
5
{
6
    tft_ili9xxx_spibb_initIo();
7
}
8
9
static inline exit_t tft_put(uint8_t cmd, const uint8_t data[], size_t len)
10
{
11
    return tft_ili9xxx_spidma_put(cmd, data[], len);
12
}
13
14
static inline exit_t tft_putRpt(uint8_t cmd, uint16_t data, size_t nRpt)
15
{
16
    return tft_ili9xxx_spidma_putRpt(cmd, data, nRpt);
17
}
18
19
/* [...Wiederholung fuer jede Funktion fuer jede Variante...] */
Mit einer vernünftigen Gaming-Maus kann man so einen Header auch schnell 
durchscrollen.



*F: "The Switcher Wrapper"*

In einer Header-Datei werden die aufzurufenden Funktionen gewrappt:
1
/* tft.h */
2
3
static inline exit_t tft_put(uint8_t cmd, const uint8_t data[], size_t len)
4
{
5
    switch( TFT_CONNEXION )
6
    {
7
        case TFT_ILI9XXX_SPI_DMA:
8
            return tft_ili9xxx_spidma_put(cmd, data[], len);
9
            
10
        case TFT_ILI9XXX_SPI_BB:
11
            return tft_ili9xxx_spibb_put(cmd, data[], len);
12
13
        case TFT_ILI9XXX_8BIT:
14
            return tft_ili9xxx_8bit_put(cmd, data[], len);
15
            
16
        case TFT_MOCKUP_SDL:
17
            return tft_sdl_put(cmd, data[], len);
18
    }
19
}
20
21
/* [...Wiederholung fuer jede Funktion...] */
Auch hier kann man mit einer vernünftigen Gaming-Maus so einen Header 
auch schnell durchscrollen.



*G: "Vtable: I'll make my own C++ with blackjack and hookers"*

Es wird ein struct mit Funktionszeigern auf die entsprechende Funktion 
angelegt und von den
aufrufenden Funktionen ausschließlich dieser benutzt.

Ein gewisser Laufzeit-Overhead ist vorhanden, aber dafür könnte man das 
Display sogar Hot-Plug-fähig gestalten.


*H: "Das Problem habe ich gar nicht."



*Welche Variante bevorzugst Du in Deinen Projekten?*

: Bearbeitet durch User
von Flo (Gast)


Lesenswert?

Displayinterface Treiber aufsplitten auf mehrere Dateien 
(tft_soft_spi.c, tft_hard_spi.c, tft_8bit.c) und nur die zur Hardware 
passende Datei im Projekt haben?

Wie oft möchtest du das Interface denn innerhalb des Projekts wechseln?

von Walter T. (nicolas)


Lesenswert?

Flo schrieb:
> Wie oft möchtest du das Interface denn innerhalb des Projekts wechseln?

Normalerweise vier, fünf mal am Tag.

Ich habe momentan 6 Targets:
 - Hardware-Revision 0.0 (Debug)  -> 8bit-LCD
 - PC-Mockup                      -> LCD-Emulation
 - Modultests auf dem PC          -> kein LCD, Konsolenausgabe
 - Modultests auf dem Target      -> kein LCD, UART-Ausgabe
 - Hardware-Revision 0.1 (Debug)  -> SPI-LCD
 - Hardware-Revision 0.1 (Release)-> SPI-LCD

Flo schrieb:
> und nur die zur Hardware
> passende Datei im Projekt haben?

Okay, das wäre eine neue Kategorie
H: "Library und viele Unterprojekte im Versionskontrollsystem", wenn ich 
das richtig verstehe?

: Bearbeitet durch User
von Gerd (Gast)


Lesenswert?

> Die Adafruit-Library gefällt mir nicht,

Du solltest dir die Arduino-Libraries aber trozdem als Beispiel nehmen. 
Dort kann man alle möglichen MCUs auswählen und der richtige Treiber 
wird kompiliert.
Bei den TFT Libraries lässt sich der Hardwaretreiber durch die Wahl der 
Schnittstellenklasse einstellen.

von A. S. (Gast)


Lesenswert?

G. Und natürlich setzt jede Implementierung die Funktionszeiger selber. 
Die Auswahl erfolgt durch die entsprechende init-Funktion.

Habe ich schon vor 20 Jahren bei dem PIC18 so gemacht.

Segger machte das bei seinem emWin auch ohne struct: eine Codezeile ist 
identisch, wenn sie eine Funktion oder einen funktionsptr aufruft.

von Walter T. (nicolas)


Lesenswert?

A. S. schrieb:
> G

Hast Du einen Codeschnipsel, wie das bei Dir aussieht? Bei mir sah der 
Versuch recht wild aus mit den ganzen unterschiedlichen typedefs, um die 
verschiedenen Parameterlisten abbilden zu können. Mit nackten 
void-Zeigern hebele ich ja Warnungen zu den Funktionsparametern komplett 
aus.

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

Walter T. schrieb:
> Es wird ein struct mit Funktionszeigern auf die entsprechende Funktion
> angelegt und von den
> aufrufenden Funktionen ausschließlich dieser benutzt.

Warum nicht einfach eine Klasse pro Variante? ggf. mit virtuellen 
Funktionen? Oder schlicht einem Typalias (typedef), welches die zu 
nutzende Klasse festlegt. Dann kann man sogar leicht zwischen 
Zur-Laufzeit-Umschalten und Beim-Kompilieren-Entscheiden wechseln.
Bei geringfügigen Unterschieden kann man der Klasse im Konstruktor auch 
eine Konfiguration mitgeben (Pin-Nummer o.ä.). Außerdem kann man 
Overloading nutzen, sodass die Funktionen alle "put" heißen und 
verschiedene Datentypen ausgeben können.

Walter T. schrieb:
> *Welche Variante bevorzugst Du in Deinen Projekten?*

Auf jeden Fall die C++-Variante... Die anderen sind nur fiese Krücken um 
die Lücken von C auszugleichen.

von Programmierer (Gast)


Lesenswert?

Hier ein Beispiel für die C++-Variante:
1
// Abstraktes Display
2
class Display {
3
  public:
4
    /* Hardware-Initialisierung */
5
    Display ();
6
    /* Beliebige Daten senden */
7
    virtual exit_t put(uint8_t cmd, const uint8_t data[], size_t len) = 0; 
8
    /* Wiederholt die gleichen zwei Bytes senden */
9
    virtual exit_t putRpt(uint8_t cmd, uint16_t data, size_t nRpt) = 0;
10
    /* 1-Bit-Bitmap schreiben ohne Puffer */
11
    virtual exit_t putBitmap(uint8_t cmd, const uint8_t *data, size_t nBit, uint16_t val0, uint16_t val1) = 0;
12
    /* Auslesen */
13
    virtual exit_t get(uint8_t cmd, uint8_t data[], size_t len) = 0;
14
};
15
16
// Konkretes Display mit SoftSPI
17
class DisplaySoftSPI final : public Display {
18
  public:
19
    /* Hardware-Initialisierung */
20
    DisplaySoftSPI ();
21
    /* Beliebige Daten senden */
22
    virtual exit_t put(uint8_t cmd, const uint8_t data[], size_t len) override; 
23
    /* Wiederholt die gleichen zwei Bytes senden */
24
    virtual exit_t putRpt(uint8_t cmd, uint16_t data, size_t nRpt) override;
25
    /* 1-Bit-Bitmap schreiben ohne Puffer */
26
    virtual exit_t putBitmap(uint8_t cmd, const uint8_t *data, size_t nBit, uint16_t val0, uint16_t val1) override;
27
    /* Auslesen */
28
    virtual exit_t get(uint8_t cmd, uint8_t data[], size_t len) override;
29
};
30
31
// Das Gleiche nochmal für DisplayHardSPI, DisplaySDL ...
32
33
// Alias definieren, z.B. so um fix immer DisplaySoftSPI zu nutzen
34
35
using MyDisplay = DisplaySoftSPI;
36
37
// Oder so, um zur Laufzeit umschalten zu können
38
39
using MyDisplay = Display;
40
41
42
// Eine Funktion, die etwas mit einem Display macht
43
void doit (MyDisplay& disp) {
44
}
45
46
int main () {
47
  // Instanz anlegen
48
  MyDisplay disp (...);
49
  
50
  // Etwas damit machen
51
  doit (disp);
52
}

Spart die Fummelei mit Funktionszeigern. Je nachdem, wie man MyDisplay 
definiert, wird eine der Schnittstellen genutzt, oder eben 
Laufzeit-Umschaltung ermöglicht. Der Trick daran ist, dass bei der 
Nutzung von "using MyDisplay = DisplaySoftSPI" o.ä. der 
Laufzeit-Overhead des Umschaltens entfällt, weil die Klasse "final" ist 
und die Aufrufe somit devirtualisiert werden, d.h. die vtables und 
indirekten Aufrufe verschwinden. Somit ist es gleichzeitig flexibel und 
immer so effizient wie möglich. Und ganz nebenbei kann man so mehrere 
Displays mit unterschiedlichen(!) Interfaces in einem Projekt 
unterbringen (viel Spaß das mit den C-Varianten zu machen).

von Walter T. (nicolas)


Lesenswert?

Walter T. schrieb:
> Hast Du einen Codeschnipsel

Ich habe das nochmal probiert, und so wild sieht es wirklich nicht aus. 
Eigentlich sieht es sogar am wenigsten schlimm von allen Varianten aus.
1
/* .h */
2
extern void (*const tft_init)(void); 
3
extern exit_t (*const tft_put)(uint8_t, uint16_t, size_t);

Der Compiler warnt zwar nicht, wenn die Konstante nicht zugewiesen wird 
und rennt dann zur Laufzeit unweigerlich in einen Nullzeiger-Fehler, 
aber dadurch, dass es eine Konstante ist, gibt es auch nur eine Stelle, 
wo die Initialisierung liegen kann.

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

Walter T. schrieb:
> /* .h */
> extern void (*const tft_init)(void);
> extern exit_t (*const tft_put)(uint8_t, uint16_t, size_t);

das baut C++ ansatzweise nach, ohne die Typsicherheit, Optimierungen und 
Erweiterungen die C++ bietet.
Da ist selbst die AdafruitGFX deutlich besser. Was machst du wenn mal 
zwei oder mehr Displays angesteuert werden sollen? Bei den kleinen OLED 
oder z.B. runden Displays durchaus wünschenswert. Dann nimmt man in C 
eine Struktur von Funktionszeigern und ist endgültig bei C++ vTables.

von Walter T. (nicolas)


Lesenswert?

Walter T. schrieb:
> Ich habe das nochmal probiert, und so wild sieht es wirklich nicht aus.

Nachtrag: Und EmBitz findet den Funktionszeiger nicht als Funktion, so 
dass man die IDE-Suchfunktionen und Auto-Vervollständigen nicht nutzen 
kann.

Johannes S. schrieb:
> das baut C++ ansatzweise nach, ohne die Typsicherheit, Optimierungen und
> Erweiterungen die C++ bietet.

Meine selbst auferlegte Regel: Ändere nie in der Mitte oder kurz vor 
Ende eines Projekts die Programmiersprache. Eine neue Programmiersprache 
fängt man am Anfang eines Mini-Projekts an.

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


Lesenswert?

Ich mache normalerweise eine Variation von G. Einfach mit 
-ffunction-sections -fdata-sections -Wl,--gc-sections kompilieren, der 
compiler wird das ungenutzte dann schon raus schmeissen. Ich mache das 
immer wieder etwas anders. Ein Beispiel wäre:

interface.h
1
#define INTERFACE_HELPER(T,RET,NAME,PARAMS) RET (*NAME) PARAMS;
2
#define INTERFACE(IFACE) struct if_##IFACE { IF_##IFACE(INTERFACE_HELPER,IFACE) };
3
4
#define IMPLEMENTATION_HELPER_1(T,RET,NAME,PARAMS) static RET impl_##T##_##NAME PARAMS; 
5
#define IMPLEMENTATION_HELPER_2(T,RET,NAME,PARAMS) .NAME = impl_##T##_##NAME,
6
#define IMPLEMENTATION(IFACE, IMPL) IF_##IFACE(IMPLEMENTATION_HELPER_1,IFACE) struct if_##IFACE IMPL = { IF_##IFACE(IMPLEMENTATION_HELPER_2,IFACE) };

tft_ili9xxx.h
1
#include <interface.h>
2
3
#define IF_tft_ili9xxx(X,T) \
4
  X(T, void, initIo, (void)) \
5
  X(T, exit_t, put, (uint8_t cmd, const uint8_t data[], size_t len)) \
6
  X(T, exit_t, putRpt, (uint8_t cmd, uint16_t data, size_t nRpt)) \
7
  X(T, exit_t, putBitmap, (uint8_t cmd, const uint8_t *data, size_t nBit, uint16_t val0, uint16_t val1)) \
8
  X(T, exit_t, get, (uint8_t cmd, uint8_t data[], size_t len)) \
9
  X(T, exit_t, reset, (void))
10
11
INTERFACE(tft_ili9xxx)

tft_ili9xxx/8bit.c
1
#include <tft_ili9xxx.h>
2
3
IMPLEMENTATION(tft_ili9xxx, tft_ili9xxx_8bit)
4
5
void tft_ili9xxx_initIo(void){ ... }
6
exit_t tft_ili9xxx_put(uint8_t cmd, const uint8_t data[], size_t len){ ... }
7
...

config.h
1
extern struct if_tft_ili9xxx tft_ili9xxx_8bit;
2
#define TFT1 tft_ili9xxx_8bit

main.c
1
#include <config.h>
2
#include <tft_ili9xxx.h>
3
4
int main(){
5
  TFT1.initIo();
6
  ...
7
}

Manchmal schreibe ich das auch ohne die macros aus. Neulich habe ich 
angefangen, statt Makros zum sparen der Schreibarbeit einen eigenen 
kleinen Parser / präprozessor zu nehmen.

https://onlinegdb.com/oH-hhxN5k

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

Walter T. schrieb:
> ihr kennt das sicher auch. Man hat plötzlich viele Funktionen, die
> praktisch das gleiche machen.

Nein, ein derartig verwickeltes Interface kenne ich aus meiner Praxis 
nicht. Aber das ist eine Planungs-Angelegenheit. Bei mir gibt es eine .h 
für den/die diversen Lowlevel-Treiber und deren Innereien gehen alle 
darüber angeordneten Schichten nichts an.

Du hingegen versuchst, alle dir nur einfallenden Ansteuer-Versionen 
(parallel, SPI, SPI+DMA usw.) mit jeweils eigenen Namen zu führen, was 
letztlich zu einem Gewirr bei der/den Headerdateien .h führt. Und das, 
obwohl es dem eigentlichen Displaytreiber egal zu sein hat, wie seine 
Erinstellwerte für den Displaycontroller zum Display geschaufelt werden 
sollen.

W.S.

von Stefan F. (Gast)


Lesenswert?

Ich habe das mal so bei einer seriellen Schnittstelle gemacht:

serial.h listet alle Funktionen auf, die man "von außen" benutzen darf.
serial.c implementiert einige davon und enthält am Ende dies:
1
#if defined(UDR) || defined(UDR0)
2
  #include "serial_atmega.c"
3
#else
4
  #include "serial_xmega.c"
5
#endif

In diesen beiden Dateien sind die restlichen hardwarespezifischen 
Varianten implementiert.

Du kannst dazu ja auch ein eigenes "define" verwenden, welches du ins 
Makefile schreibst, bzw. in die Projektkonfiguration.

von Programmierer (Gast)


Lesenswert?

W.S. schrieb:
> Und das,
> obwohl es dem eigentlichen Displaytreiber egal zu sein hat, wie seine
> Erinstellwerte für den Displaycontroller zum Display geschaufelt werden

Kommt drauf an - das klappt nicht, wenn das Übertragungsprotokoll von 
der darunter liegenden Schicht abhängig ist. Selbst im OSI-Modell ist 
das so - die oberen Schichten müssen wissen, auf welcher Schicht sie 
aufsetzen, und sich dementsprechend verhalten. Wenn z.B. IPv4 auf 
Ethernet eingesetzt wird, muss IPv4 das ARP benutzen.

Daniel A. schrieb:
> Neulich habe ich
> angefangen, statt Makros zum sparen der Schreibarbeit einen eigenen
> kleinen Parser / präprozessor zu nehmen.

Typisch C - eine eingeschränkte Sprache verwenden und mit eigenen 
Präprozessoren aufbohren, statt eine Sprache zu nutzen die solche 
Probleme nicht hat...

von W.S. (Gast)


Lesenswert?

Programmierer schrieb:
> Typisch C

Nö, das ist nicht typisch "irgendeine Programmiersprache", sondern 
typisch nicht genug geplant, bevor mit dem Quellcodeschreiben angefangen 
wurde.

So herum.

W.S.

von Walter T. (nicolas)


Lesenswert?

Wie würdest Du das denn sinnvollerweise perfekt vorab geplant angehen?

Ich kann mir gerade nicht wirklich vorstellen, wie es sinnvoll gehen 
soll, alles in eine Funktion zu bringen, die wahlweise an µC-Pins 
wackelt, eine SPI-Peripherie oder eine Windows-Library aufruft.

: Bearbeitet durch User
von 123 (Gast)


Lesenswert?

Die frage ist wer entwickelt / pflegt software für verschiedene 
entwicklungssstände einer HW. vor allem wenn sie so wie es hier sich 
anhört um HW protypen handelt? Die wenigsten. Ein Alter protyp wird 
nicht mehr mit neuer SW versogt. (oder nur für einen begrenzten 
zeitraum.) ist einfach zu viel aufwand.

Dein PC code hört sich für mich wie testing an. hat damit eigentlich 
nichts im Produktiv code zu suchen. Ja er verwendet die sorcen daraus, 
... Ja da sind code schnitstellen die sich nicht so einfach ändern 
dürfen, ... aber die mocups für nicht vorhandene HW und die umschaltung 
sollten für mich nicht im produktiv code rumfliegen.

Sicher es gibt leute die müssen aufgrund von Bauteil abkündigungen, 
preis ersparnissen, ... die gleiche SW auf verschiedene HW bringen, ... 
die haben dann aber auch ein passendes Varianten management, ... und 
verwalten z.B. die varianten dann auch getrennt im git und mergen die 
änderungen von a nach b bzw wieder zurück.

von Thomas Z. (usbman)


Lesenswert?

ich hab das vor einiger Zeit wie folgt gelößt:
Auf unterster Ebene hab ich ein SPI Treiber, von dem gibt es mehrer 
Varianten
zum Einen HW SPI (2 mal weil ich 2 HW SPI Schnittstellen habe) und 
Software SPI. Darüber liegt die Schicht mit der log Ansteuerung des / 
der Displays
Darüber dann der Ausgabe Layer (Line, Char, Circle ...)
Kommt ein neues Display muss halt der HW Treiber neu gemacht werden (I2C 
oder par). Dann ev noch die Display Ebene und fertig.

Ich gehe da mit W.S. konform. Deine App kommt nur mit dem Ausgabe Layer 
in Kontakt der im Idealfall immer gleich ist. Ganz komfortabel ist es 
dann das ganze in Libs bereitzuhalten die nur noch zum Code gelinkt 
werden müssen.

von Walter T. (nicolas)


Lesenswert?

123 schrieb:
> Dein PC code hört sich für mich wie testing an. hat damit eigentlich
> nichts im Produktiv code zu suchen.

Den schönen Test-Code, der der einzige Grund ist, warum ich mich traue, 
alte Klamotten überhaupt noch einmal anzufassen entfernen? Gefällt mir 
gar nicht. Da ich kein hauptberuflicher Software-Entwickler bin, ich für 
mich jeder Stand ein Entwicklungsstand.

123 schrieb:
> ein passendes Varianten management, ... und
> verwalten z.B. die varianten dann auch getrennt im git und mergen die
> änderungen von a nach b bzw wieder zurück.

Okay, das ist Variantenmanagement auf Dateiebene. Passt grob unter "H" 
oder wenn man die Versions-/Variantenverwaltung als Teil des 
Build-Systems sieht unter "A".


Thomas Z. schrieb:
> Deine App kommt nur mit dem Ausgabe Layer
> in Kontakt der im Idealfall immer gleich ist.

Die "App" oder der Rest der Firmware: Ja. Aber hier geht es ja gerade um 
den "glue layer" (ich habe kein besseres Wort dafür).

Thomas Z. schrieb:
> Ganz komfortabel ist es
> dann das ganze in Libs bereitzuhalten die nur noch zum Code gelinkt
> werden müssen.

Also machst Du Variante A.

: Bearbeitet durch User
von Thomas Z. (usbman)


Lesenswert?

Walter T. schrieb:
> Also machst Du Variante A.

naja um ehrlich zu sein es gibt wohl immer auch ein paar Zwitter 
Lösungen. Das mit den Libs ist ja auch Verwaltungsaufwand. Für ein 
simples char LCD lohnt sich das natürlich nicht. Ich hab mir aber schon 
einiges von kommerziell erhältlichen FW Bibliotheken abgeschaut.
In manchen Fällen hatte ich auch  schon mal Mokups auf dem PC zu 
Demozwecken programmiert um ein Gefühl für das Look & Feel zu bekommen. 
Das waren dann aber immer größere Projekte wo ich nur einen (kleinen) 
Teil beisteuerte.

von EAF (Gast)


Lesenswert?

Walter T. schrieb:
> a. Aber hier geht es ja gerade um
> den "glue layer" (ich habe kein besseres Wort dafür).

In der C++ Welt könnte "glue layer"  eine Implementierung des "Bridge"- 
oder "Adapter Design Pattern" sein.
Evtl. kannst dir da ja was (zumindest die Idee) abschauen.....

von Tom K. (ez81)


Angehängte Dateien:

Lesenswert?

Ich habe ein ähnliches Problem (allerdings nicht embedded) mit A gelöst 
und cmake den schwierigen Teil überlassen. Dass die IDE beim Klick auf 
die Funktion nicht in der gewollten Implementierung landet, lässt sich 
wohl nicht vermeiden.

Mini-Beispiel im Anhang, die Library-Varianten würde man 
sinnnvollerweise auf mehr Verzeichnisse verteilen und vielleicht auch 
nicht alles auf einmal bauen.

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.