Hallo zusammen,
suche nach einer Umsetzung für eine Art Softwarepool.
Ich habe einen Schwung gleicher Hardwaremodule mit einem ATmege168A.
Diese Module werden auf verschiedenen Erweiterungen eingesetzt
(sozusagen Adaptionen). Jetzt möchte ich mir Softwaretechnisch das Leben
natürlich einfach machen und allgemeine Funktionen an einer zentralen
Stelle sammeln.
Dazu habe ich mir (in AVR Studio 5 mit AVRGCC) eine Solution angelegt
die einmal eine Static Library enthält und für jede Hardware Erweiterung
ein weiteres Projekt.
So habe ich z.B. die allgemeinen UART Sende- und Empfangsfunktionen in
der Lib und nutze sie in den verschiedenen Erweiterungen. Dazu wird beim
Compilieren als erstes die Lib erstellt welche dann bei dem jeweiligen
Projekt mit dazu gelinkt wird.
Das funktioniert bisher auch ganz gut. Doch jetzt habe ich z.B. den
Fall, dass ich bei den UART Funktionen bei einem Modul das XON / XOFF
Handshaking benötige. Bei allen anderen Modulen nicht.
Klar könnte ich jetzt bei allen Funktionen die es betrifft eine zweite
Version erstellen und mal diese oder mal jene verwenden. Schwierig wird
es da dann bei ISR's.
Eine Lösung mit #defines & Co die eine Umbenennung der Funktionsnamen in
den Projekten durchführen möchte ich möglichst vermeiden, da ich dieses
getrickse mir für Fälle aufheben möchte wo sonst nichts mehr geht. (z.B.
wenn in der Zukunft sich die Hardware ändert und für die selbe
Funktionalität verschiedene Softwaren nötig werden.)
Gibt es sonst noch bekannte / erprobte Lösungen?
Vielen Dank!
Hallo,
du schreibts du erstellst die Lib bei jedem Compilieren neu ?!
Dann kannst Du doch für's Lib erstellen eine Headerdatei einbinden, die
dir den Code in der Lib configuriert.
In etwa so --> Pseudocode !
1
config.h
2
#define XON_XOFF 1
3
#define FREQUENCY 20
In den Sourcen zur Lib bindest Du das Header-File ein und läßt den
Pre-Prozessor die Arbeit machen. Du hast also eine Code-Basis die alles
kann was du brauchst, und schaltest die benötigte Funktionalität über
die Konfiugration frei.
Dirk R. schrieb:> Dazu habe ich mir (in AVR Studio 5 mit AVRGCC) eine Solution angelegt> die einmal eine Static Library enthält und für jede Hardware Erweiterung> ein weiteres Projekt.
Würde ich nicht machen, weil Du dann genau das Problem hast, dass die
Static-Library nur auf dem ATmega168A funktioniert und Du keine
projektspezifischen Unterscheidungen mehr machen kannst.
Besser ist es, den gemeinsamen C-Code in einem gemeinsamen Ordner
abzulegen und die Dateien als Links in jedes Projekt einzufügen. Dann
können sie für jedes Projekt mit den projektspezifischen Optionen
kompiliert werden.
Schau mal in diesen Artikel, da habe ich eine Möglichkeit zur Aufteilung
von Code und Konfigurationsdateien vorgestellt:
http://www.mikrocontroller.net/articles/Plattformunabh%C3%A4ngige_Programmierung_in_C#Organisation_der_Quellcode-Dateien
Fabian O. schrieb:> Würde ich nicht machen, weil Du dann genau das Problem hast, dass die> Static-Library nur auf dem ATmega168A funktioniert und Du keine> projektspezifischen Unterscheidungen mehr machen kannst.>> Besser ist es, ...
Oder noch besser (IMO), man lässt "on demand" individuelle private
Libraries generieren. Das Makefile der Library bekommt dazu "von Außen"
Variablen (mindestens MCU-Typ und Obj-Ordner)(*) mit auf den Weg, mit
deren Hilfe die Library (und auch die zugrunde liegenden Objekt-Dateien)
dann innerhalb des jeweiligen Projektes (in einem Unterordner) generiert
wird.
Letztlich hat das dann den selben Effekt wie
> den gemeinsamen C-Code in einem gemeinsamen Ordner> abzulegen und die Dateien als Links in jedes Projekt einzufügen. Dann> können sie für jedes Projekt mit den projektspezifischen Optionen> kompiliert werden.
nur dass man einem Projekt nicht einen Wust an "externen" C-Files
hinzufügen muss, sondern nur einen make-Aufruf als Pre-Build-Step.
(*) wenn mehr Individualität nötig ist (wie anscheinend beim OP), dann
kann man zusätzlich auch den Pfad zu einem Library-Config-Header-File
innerhalb des Projektes mitgeben, der dann vom Library-Makefile an die
Sourcen dort weitergereicht wird.
In meinen Projekten Löse ich das auf Quellebene über ein "Repository",
das C- und H-Dateien enthält, die je nach Bedarf automatisch ins
aktuelle Projekt kopiert werden.
Kopiert wird deshalb, weil es Module gibt wie
1
/* modul.c */
2
3
#include"modul.h"
4
5
/* C-Code */
und modul.c unverändert in den Projekten verwendet werden kann, nicht
jedoch modul.h, das pro Projekt zur Verfügung gestellt werden muß.
Im Makefile ist das Kopieren nur eine Zeile:
1
$(COMMON_SRC): % : $(COMMON)/%
2
cp $< $@
Dan Rest regeln die Abhängigkeiten und die Definition von COMMON und
COMMON_SRC.
Eine eigene Lib dafür zu erstellen erscheint mir da als Overkill, zumal
man die Bibliothek als Multilib erstellen muß oder jedesmal neu erzeugen
muß um Derivat-unabhängig zu sein oder nach Makros parametrisieren zu
können.
Zudem sind manche Optimierungen wie LTO nur schwierig mit
Bibliothekscode anwendbar, d.h. wenn man Bibliothekscode inlinen will,
muß sowohl die Bibliothek richtig erzeugt sein als auch ein Linker mit
Plugin-Support verfügbar sein.
>> und modul.c unverändert in den Projekten verwendet werden kann, nicht> jedoch modul.h, das pro Projekt zur Verfügung gestellt werden muß.
Und bei meinem Konzept würde dort dann
1
#include MODUL_HEADER
stehen, und MODUL_HEADER würde im Library-Makefile definiert werden mit
einem "von Außen" herein gereichten Projekt spezifischen Pfad.
Johann L. schrieb:> Eine eigene Lib dafür zu erstellen erscheint mir da als Overkill
Das kann man so pauschal doch gar nicht sagen. Wenn es nur eine C-Datei
ist, logisch. Und wenn die gemeinsam zu nutzende Code-Basis nun
irgendein nicht-trivialer Protokoll-Stack ist?
Wie so oft ist das letztlich zu einem großen Teil eine Frage
persönlicher Vorlieben. ;-)
Johann L. schrieb:> Kopiert wird deshalb, weil es Module gibt wie/* modul.c */>> #include "modul.h">> /* C-Code */>> und modul.c unverändert in den Projekten verwendet werden kann, nicht> jedoch modul.h, das pro Projekt zur Verfügung gestellt werden muß.
Warum musst Du die deshalb kopieren? Du kannst doch modul.c und modul.h
in verschiedenen Ordnern liegen haben.
Überhaupt, warum änderst Du modul.h in jedem Projekt? Darin sollten die
Prototypen passend zu den öffentlichen Funktionen in modul.c stehen.
Wenn sich in modul.c eine Funktion ändert oder eine neue dazu kommt,
musst Du sonst ja alle Versionen von modul.h in den Projekten einzeln
nachziehen ...
Deshalb würde ich die projektspezifischen Einstellungen in eine eigene
Datei modul_config.h packen und die entweder in modul.c oder modul.h
inkludieren, je nachdem ob der Benutzer des Moduls diese Einstellungen
sehen muss oder nicht. Dann muss man modul.h nur anfassen, wenn sich an
der Schnittstelle etwas ändert, und das gilt dann für alle Projekte.
Bringt ja nichts, eine modul.c und modul.h zu haben, die nicht
zusammenpassen.
Fabian O. schrieb:> Deshalb würde ich die projektspezifischen Einstellungen in eine eigene> Datei modul_config.h packen
Ich gehe mal davon aus, dass er genau das gemeint hatte, und ihm nur die
Namensgebung im Beispiel etwas "ausgerutscht" ist.
Stefan Ernst schrieb:> Wie so oft ist das letztlich zu einem großen Teil eine Frage> persönlicher Vorlieben. ;-)
ACK.
Fabian O. schrieb:> Johann L. schrieb:>> Kopiert wird deshalb, weil es Module gibt wie/* modul.c */>>>> #include "modul.h">>>> /* C-Code */>>>> und modul.c unverändert in den Projekten verwendet werden kann, nicht>> jedoch modul.h, das pro Projekt zur Verfügung gestellt werden muß.>> Warum musst Du die deshalb kopieren? Du kannst doch modul.c und modul.h> in verschiedenen Ordnern liegen haben.
Ja, kann ich.
Ich hab eben nur vogestellt, wie ich das handhabe. Und dass es auch
ohne Library geht. Irgendwann hab ich mich für diesen Ansatz
entschieden und bislang gab es meinerseitz kein Bedürfnis, das zu
änderen -- eben weil es für mich problemlos funktioniert und mir ne
eigene Library zu viel des Wolfes war / ist.
Wie so oft in der Programmiererei gibt es kein "richtig" oder "falsch",
sindern eben mehrere unterschiedliche Ansätze, denen man nach Gusto
folgen kann. Und das ist auch gut so.
Andere sind in der Auswahl der Ansätze vielleicht eingeschränkter, weil
sie ne bestimmte IDE verwenden, die das Projektmanagement oder den
Build-Prozess vorgibt.
Kopieren hat auch den Vorteil, daß man mit einer lokelen Kopie
rumspielen und diese ändern kann, weil sie (selten der Fall) eine
Erweiterung braucht.
Alternative Möglichkeit wäre, direkt ein SCM-Repository einzuklinken
(svn, git, bazaar, ...) oder das jeweils benötigt Standard-Geraffel wie
Taster, DCF-Decoder, UART, Morsen, Timer, Weiß-der-Teufel zu
Projektbeginn von Hand zu kopieren.
> Überhaupt, warum änderst Du modul.h in jedem Projekt? Darin sollten die> Prototypen passend zu den öffentlichen Funktionen in modul.c stehen.
In einem Fall geht es um Countdown-Zähler, mit denen "langsame" Zeiten
gemacht werden wie: Timeout für einen Bildschirmschoner, Level-abhängige
Geschwindigkeitssteuerung von Spielen, LED-Blinker, etc. Welche Zähler
gebraucht werden ist natürlich 100% vom Projekt abhängig, die
Bedienung der Zähler im zugehörigen C-Modul hingegen nicht, d.h.
modul.c kann in allen Projekten, die Timer verwenden gleich sein.
Jedenfalls funktionieren diese Zähler so gut und mit so wenig Overhead,
daß ich bisher in keiner meiner Projekte auch nur ansazuweise das
Bedürfnis nach Multithreading oder Tasks oder was auch immer hatte.
Und bei Harten Echtzeitanforderungen wie zB Darstellung eines
Spielgeschehens auf einer Elektronenstrahlröhre, kann man mit dem
Overhead eines Taskwechsels echt nix anfangen.
> Wenn sich in modul.c eine Funktion ändert oder eine neue dazu kommt,> musst Du sonst ja alle Versionen von modul.h in den Projekten einzeln> nachziehen ...
In dem Fall von obn äbdern sich keine Funktionen, sondern lediglich
Datenstrukturen. Ist zwar eher ungewöhnlich das, ist aber so :-)
Johann L. schrieb:> Kopieren hat auch den Vorteil, daß man mit einer lokelen Kopie> rumspielen und diese ändern kann, weil sie (selten der Fall) eine> Erweiterung braucht.
Überschreibt Dein Makefile diese Änderungen nicht gleich wieder?
> Alternative Möglichkeit wäre, direkt ein SCM-Repository einzuklinken> (svn, git, bazaar, ...)
Bei sehr vielen Projekten dürfte das die beste Lösung sein. Wenn alle
Projekte auf die gleichen Module zugreifen, muss man halt auch immer
alle ändern, wenn es inkompatible Änderungen in der Schnittstelle gibt.
Wenn jedes Projekt seine eigenen lokalen Kopien hat, kann man dagegen in
älteren Projekten einfach die älteren Versionen lassen, mit denen sie
funktionieren. Andererseits kann man dank Repository bei Bedarf mit
einem Klick auf die aktuellste (oder jede beliebige andere) Version
updaten.
> oder das jeweils benötigt Standard-Geraffel wie> Taster, DCF-Decoder, UART, Morsen, Timer, Weiß-der-Teufel zu> Projektbeginn von Hand zu kopieren.
Das wird fürchterlich unübersichtlich, wenn man mit der Zeit überall
verschiedene Versionen mit verschiedenen Features und Bugs rumfliegen
hat.
Johann L. schrieb:> Welche Zähler> gebraucht werden ist natürlich 100% vom Projekt abhängig, die> Bedienung der Zähler im zugehörigen C-Modul hingegen nicht, d.h.> modul.c kann in allen Projekten, die Timer verwenden gleich sein.
Zur Bedienung gehört NUR modul.h. Da steht für mich als Benutzer
drinnen, wie ich das Modul zu bedienen habe. modul.c hat mich nicht zu
interessieren. Wenn ich da reinschaun muss, ist schon was falsch
gelaufen. Dass ich als Benutzer die Bedienungsvorschrift des Moduls
(modul.h) unter meiner Kontrolle habe (indem ich sie ändern kann/soll),
ist ziemlich unlogisch und keine saubere Trennung der Zuständigkeiten.
Leider machen das viele C-Module so, die in ihrer (einzigen) Headerdatei
irgendwelche Einstellungen definieren (Puffergrößen, Ports, ...), die
der Benutzer ändern soll. Das ist da absolut fehl am Platz. Bei jedem
Update müsste man die Einstellungen in der Headerdatei wieder
nachziehen. Oder man läuft der Gefahr, dass sie nicht mehr zur
Implementierung passt.
Klar, im Hobbybereich für sich selber kann man das machen wie man will.
Aber wenn der Code auch von anderen verwendet wird, ist das keine Frage
persönlicher Vorliebe mehr, sondern von sauberem Softwaredesign.
Fabian O. schrieb:> Johann L. schrieb:>> Kopieren hat auch den Vorteil, daß man mit einer lokelen Kopie>> rumspielen und diese ändern kann, weil sie (selten der Fall) eine>> Erweiterung braucht.>> Überschreibt Dein Makefile diese Änderungen nicht gleich wieder?
Nein. Änderungen machen Dateien neuer, nicht älter.
>> Alternative Möglichkeit wäre, direkt ein SCM-Repository einzuklinken>> (svn, git, bazaar, ...)>> Bei sehr vielen Projekten dürfte das die beste Lösung sein. Wenn alle> Projekte auf die gleichen Module zugreifen, muss man halt auch immer> alle ändern, wenn es inkompatible Änderungen in der Schnittstelle gibt.
Das ist aber ein generelles Problem von Code-Sharing. D.h. wenn der
Code Projektübergreifend verwendet wird, ohne daß er ausgegoren ist,
dann ist es nicht unwahrscheinlich, daß inkompatible Änderungen
erforderlich werden.
Der Code sollte also eine gewisse Reife haben, bevor er geminsam
verwendet wird — was aber unabhängig von der Verwendung per Quelle oder
Bibliothek ist. Und auch bei Verwendung eines SCM hat man die
Problematik. Zwar hat man damit Versionierung, aber wenn man durch den
Zoo von Versionen nicht mehr durchblickt wird das auch nix helfen...
> Johann L. schrieb:>> Welche Zähler>> gebraucht werden ist natürlich 100% vom Projekt abhängig, die>> Bedienung der Zähler im zugehörigen C-Modul hingegen nicht, d.h.>> modul.c kann in allen Projekten, die Timer verwenden gleich sein.>> Zur Bedienung gehört NUR modul.h. Da steht für mich als Benutzer> drinnen, wie ich das Modul zu bedienen habe. modul.c hat mich nicht zu> interessieren.
modul.c interessiert auch nicht. Aber es wird eben gebraucht und daher
aus der Codebases genommen — ansonsten gibt's ein Compiler- oder
Linkerfehler.
> Wenn ich da reinschaun muss, ist schon was falsch gelaufen.> Dass ich als Benutzer die Bedienungsvorschrift des Moduls> (modul.h) unter meiner Kontrolle habe (indem ich sie ändern kann/soll),> ist ziemlich unlogisch und keine saubere Trennung der Zuständigkeiten.
Warum soll ich in einem Modul 10 Timer haben, obwohl nur 2 genraucht
werden, nur weil ein anderes Projekt 10 Timer hat?
Im o.g. Falle ist das modul.h daher projektabhängig. Gleichwohl weiß
modul.c, wie es mit modul.h und den datenstrukturen darin umzugehen hat.
> Leider machen das viele C-Module so, die in ihrer (einzigen) Headerdatei> irgendwelche Einstellungen definieren (Puffergrößen, Ports, ...), die> der Benutzer ändern soll. Das ist da absolut fehl am Platz. Bei jedem> Update müsste man die Einstellungen in der Headerdatei wieder> nachziehen.
Nein, wie kommst du darauf?
> Oder man läuft der Gefahr, dass sie nicht mehr zur> Implementierung passt.
Auch nicht der Fall, natürlich passt das.
Deine countdown.h besteht aus zwei Teilen: Ein Teil ist die
Konfiguration der ganzen Counter, der andere ist die öffentliche
Schnittstelle von countdown.c.
Gut, in Deinem Fall ist Schnittstelle wirklich nicht groß, aber sie
gehört imo in eine eigene Datei:
1
// countdown.h
2
3
#include"countdown_config.h"
4
5
staticinline
6
voidwait_10ms(constuint8_tt)
7
{
8
#ifdef __AVR__
9
asmvolatile(" ; BARRIER":::"memory");
10
#endif // AVR
11
count.ms10.wait_10ms=1+t;
12
while(count.ms10.wait_10ms);
13
}
14
15
// Alle 10 ms aufrufen wenn COUNTDOWN_US100 nicht define'd ist.
16
// Alle 100 µs aufrufen wenn COUNTDOWN_US100 define'd ist.
17
externvoidjob_countdown(void);
Das sind Sachen, die von countdown.c abhängen bzw. zur Implementierung
gehören und nicht vom Projekt. Du kopierst sie aber in jedes Projekt.
Wenn Du daran mal was ändern willst oder was hinzufügen, kannst Du es in
allen Projekten von Hand nachziehen.
Die Definition von countdown_t ist dagegen projektspezifisch. Wobei
diese Elemente "für internen Gebrauch" auch nicht in der
benutzerdefinierten Konfiguration stehen sollten. Wenn Du an der
Implementierung was änderst und diese internen Elemente deswegen anders
sein müssen, kannst Du es auch wieder in allen Projekten nachziehen.
Man könnte stattdessen z.B. für jeden Countertyp einen eigenen typedef
oder ein Makro anlegen, die projektspezifisch definiert werden, und die
dann in die Gesamtstruktur einbinden. Die Gesamtstruktur würde in
countdown.c liegen, also dort, wo sie den Benutzer nichts angeht. Dort
kannst Du den Rahmen dann ändern, wie Du magst, ohne die
projektspezifischen Konfigurationen anfassen zu müssen.
Projektspezifische Konfiguration:
1
// countdown_config.h
2
3
typedefstruct
4
{
5
// Ab hier die benötigten 8-Bit Countdown-Zähler eintragen
6
}countdown_us100_t;
7
8
typedefstruct
9
{
10
// Ab hier die benötigten 8-Bit Countdown-Zähler eintragen
11
uint8_twait_10ms;
12
uint8_tblink;
13
uint8_tmorse;
14
// ...
15
}countdown_ms10_t;
Implementierung:
1
// countdown.c
2
3
#include"counterdown.h"
4
5
typedefstruct
6
{
7
#ifdef COUNTDOWN_US100
8
// Die Kompoinente muss mindestens das erste Elemenet enthalten
9
struct
10
{
11
// Erstes Element ist für internen Gebrauch!
12
uint8_ttimer_10ms;
13
countdown_100us_tuser;
14
}us100;
15
#endif /* COUNTDOWN_US100 */
16
17
// Die Kompoinente muss mindestens das erste Elemenet enthalten
18
struct
19
{
20
// Erstes Element ist für internen Gebrauch!
21
uint8_ttimer_1s;
22
countdown_ms10_tuser;
23
}ms10;
24
25
// ...
26
}countdown_t;
In dem Fall ists wahrscheinlich so hochspeziell, dass Du daran nicht oft
was ändern musst. Allgemein ists aber eben aus meiner Sicht nicht sauber
getrennt, weil in countdown.h Konfiguration, Schnittstelle und
Implementierung miteinander gemischt sind.
Ich habe für jedes Projekt einen Ordner und darin einen Makefile, der
definiert, was dieses Projekt besonderes braucht, und zählt auch die
erforderlichen Quellen aus der Bibliothek auf:
**** Start projekt21/makefile
1
PRG=projekt21
2
3
DEFS = \
4
-DNPRGPATH=$(NPRGPATH) \
5
-DF_CPU=16000000UL \
6
-DNCHAN=2 \
7
8
OBJ = $(PRG).o \
9
serial.o \
10
blink.o \
11
12
MCU=t2313
13
include ../Lib/makedefs
**** Ende projekt21/makefile
Dadurch, dass makedefs hier eingebunden ist, werden die .o-Dateien im
Ordner projekt21 abgelegt, obwohl die Quellen zum Teil aus ../Lib
kommen.
Dabei hilft die Regel: %.o : ../Lib/%.cpp
**** Start Lib/makedefs