Forum: Compiler & IDEs untersch. Kofigurationen in versch. Projekten


von Asterix-007 (Gast)


Lesenswert?

Moin Männers,

ich mache mir seit Tagen Gedanken, wie ich meine Projekte sauber 
konfigurieren kann. Kernpunkt des Problems ist folgendes:

Ich habe verschiede Bibliotheken (SPI, LCD, USART.... )
Dort werden spezifische Register und Portsettings genutzt.
Wenn ich nun in verschiedenen Projekten diese Dateien einbinde, habe ich 
früher oder später Spaghetti-Code, zumindest was die Konfiguration der 
Register und Ports betrifft.

Wie löst ihr das? Einfach in das main-file alle Registersettings hinein 
oder bindet ihr ein separates (z.B.) *.cfg-file mit ein.

Für ein paar Vorschläge wäre ich sehr dankbar!

mfg

Ulf

von Oliver (Gast)


Lesenswert?

>Wenn ich nun in verschiedenen Projekten diese Dateien einbinde, habe ich
>früher oder später Spaghetti-Code,

Warum?

Beispiel LCD: lcd.c und lcd.h. Die werden nicht "eingebunden", zumindest 
von der lcd.h bekommt jedes Projekt seine eigene Kopie, in dem alles 
projektspezifische entsprechend angepasst wird.

In lcd.c gibt es dazu eine Funktion lcd_init(), die muß einmal von main 
aufgerufen werden, fertig.

Oliver

von Markus B. (Firma: Embedit Mikrocontrollertechnik) (_mb_)


Lesenswert?

Hi, ich bastle gerade an einer eigenen kleinen Library. Ich löse das 
Problem mit einer config.h Datei, die man in jedes Projektverzeichnis 
kopiert. Ein Beispiel dazu findest du hier

http://www.wiki.elektronik-projekt.de/mikrocontroller/avr/avr-gcc-library/systick

von Asterix-007 (Gast)


Lesenswert?

Moin,

@Oliver: ja, so habe ich es bis jetzt gelöst und mir gerade eine 
klassische Überschreibung meines SPI-Registers eingehandelt. War schnell 
gefunden, aber vom Konzept her auf Dauer nicht so gut.

Das Problem sind auf der einen Seite die versteuten Register-Sttings 
z.B. Timer und Interrupt-Einstellungen und die Unübersichtlichkeit des 
Ganzen. Auf der anderen Seite stehen die modulare Wartbarkeit und 
schnelle Portierbarkeit der einzelnen Dateien.

Mit einem zentralen config.h-File z.B. wären alle Settings in einem File 
und genau für das Projekt (und diese eine Hardware)zugeschnitten.

Evtl. kann man die Brücke mit defines zu den einzelnen Modulen schlagen.
Da muss ich mal darüber nachdenken.

Danke erst mal!
Vielleicht gibt es ja noch andere Gedanken?

mfg
Ulf

von Markus B. (Firma: Embedit Mikrocontrollertechnik) (_mb_)


Lesenswert?

Asterix-007 schrieb:

> Das Problem sind auf der einen Seite die versteuten Register-Sttings
> z.B. Timer und Interrupt-Einstellungen und die Unübersichtlichkeit des
> Ganzen. Auf der anderen Seite stehen die modulare Wartbarkeit und
> schnelle Portierbarkeit der einzelnen Dateien.

Ich versuche, das in meiner Lib durch jede Menge #if defined() 
abzufangen. In meinen Augen gehört eine lcd.h nicht in das Projekt. Man 
kopiert ja auch die iom8.h in das Projekt sondern importiert die io.h 
und überlässt ihr den Rest.

Wenn man die lcd.h (und andere) immer in die Projekt Ordner kopiert und 
die Anpassungen an den Controller "hart" einfügt wünsche ich viel Spaß, 
wenn man mal einen anderen Controller braucht oder wenn sich an der Lib 
etwas ändert

Deshalb kann man lcd.c und lcd.h in einem gemeinsamen Verzeichnis 
ablegen, auf das alle Projekte zugreifen können. Die projektspezifischen 
Einstellungen kommen in eine unabhängige Datei und man kann jederzeit 
einen anderen Controller auswählen und neu compilieren.

von Oliver (Gast)


Lesenswert?

>Deshalb kann man lcd.c und lcd.h in einem gemeinsamen Verzeichnis
>ablegen, auf das alle Projekte zugreifen können.

Auch dann wünsche ich viel Spaß, wenn sich an der "Lib" etwas ändert, 
und mehrere (vor allem "fertige"), Projekte davon abhängen, gegen die 
bei Äanderungen nicht mehr sinnvoll getestet werden kann.

Oliver

von Markus B. (Firma: Embedit Mikrocontrollertechnik) (_mb_)


Lesenswert?

Oliver schrieb:
> Auch dann wünsche ich viel Spaß, wenn sich an der "Lib" etwas ändert,
> und mehrere (vor allem "fertige"), Projekte davon abhängen, gegen die
> bei Äanderungen nicht mehr sinnvoll getestet werden kann.

Das stimmt schon, aber dann reicht es auch nicht, nur die lcd.h (um bei 
dem Beispiel zu bleiben) in das Projekt zu kopieren. Dann musst du für 
jedes Projekt die komplette Lib (oder zumindest die nötigen Teile) in 
das Projektverzeichnis kopieren.

Das kann man natürlich machen. Hat alles seine Vor- und Nachteile. Ich 
persönlich kombiniere das. Projekte, die in Arbeit sind, greifen alle 
auf eine gemeinsame Lib zu. Abgeschlossene Projekte kommen zusammen mit 
dem aktuellen Stand der Lib in ein Verzeichnis und können da jederzeit 
wieder compiliert werden.

<Ironie>Wenn man es auf die Spitze treiben wollte müsste man auch den 
Compiler, das Betriebssystem und den PC erhalten. In 20 Jahren kann man 
vermutlich eh nichts mehr davon gebrauchen</Ironie>

von Oliver (Gast)


Lesenswert?

><Ironie>Wenn man es auf die Spitze treiben wollte müsste man auch den
>Compiler, das Betriebssystem und den PC erhalten. In 20 Jahren kann man
>vermutlich eh nichts mehr davon gebrauchen</Ironie>

In 20 Jahre nicht, aber in 5 schon - und wenn bis dahin zig neue 
Versionen von IDE/Compiler/Systemlibs erschienen sind, schadet es gar 
nicht, wenn man die ursprünglich verwendeten Versionen mit archiviert 
hat.

Um aber aufs Ursprungsthema zurückzukommen:
Egal, ob jetzt source und header-files beide lokal ins Projekt kopiert 
weden, oder nicht, alles mit EINER config-Datei zu konfigurieren zu 
wollen, wie weiter oben mal vorgschlagen wurde, ist wenig sinnvoll. 
Dafür baut man ja extra einzelne Module, um die modular zusammensetzen 
zu können.

Oliver

von Markus B. (Firma: Embedit Mikrocontrollertechnik) (_mb_)


Lesenswert?

Oliver schrieb:
> Dafür baut man ja extra einzelne Module, um die modular zusammensetzen
> zu können.

Naja, das kann man sehen wie man will. Ich habe auch einzelne Module und 
nutze nur die, die ich auch brauche. Aus meiner Sicht funktioniert das 
genauso gut wie dein Ansatz.

Vielleicht wird die config.h auch irgendwann unübersichtlich, aber ich 
möchte später noch ein GUI Tool schreiben, das die config.h automatisch 
erstellt.

Wie gesagt, es hat alles Vor- und Nachteile. Aber ich denke, ich kopiere 
ja auch die avr-libc nicht in jedes Projekt, warum sollte ich es dann 
mit meiner tun? Ich sehe das eher als Erweiterung der avr-libc nur mit 
dem Unterschied, das man dort nichts konfigurieren muss.

von Peter D. (peda)


Lesenswert?

Also die ganzen Pinzuweisungen mache ich immer in der "main.h" mit 
Bitmacros.

Die *.h der einzelnen Peripherietreiber enthalten ja nur die 
Funktionsnamen und globalen Variablen, da muß nichts geändert werden.

Auch die Treiber selber müssen in der Regel nicht angefaßt werden.

Bei mancher eingebauten Peripherie der AVRs (z.B. Timer, UART) hat Atmel 
aber einen ziemlichen Wildwuchs veranstaltet, was die Funktionen und die 
Namensvergabe betrifft. Die muß man an nen anderen AVR per Define 
anpassen, soweit möglich.


Peter

von Asterix-007 (Gast)


Lesenswert?

Hallo,
hm, langsam entwickelt sich ja eine Diskussion. Schön.
Um noch einmal zum Ausgang des Problem zu kommen, es ging mir um 
folgendes:
Eine LCD-Port, z.B., liegt heute auf Port A und morgen auf Port B, Port 
C ist heute Eingang und Port D Ausgang und morgen wieder anders.
Aktuell habe ich mir meinen Port B komplett auf Ausgang geschalten und 
somit den SPI-Port totgelegt( 2x DDR und Port eingestellt).
Es ging mir hauptsächlich um die projektspezifischen Settings, welche 
nur in diesem Projekt gelten. Also alle In- u. Out-Register, Timer, SPI, 
.....
Es macht z.B. wenig Sinn den Port für das LCD in der Library 
festzulegen, weil er dann wieder überschrieben werden könnte und sich 
ausserdem in jedem Projekt ändert.

Das natürlich eine Versionskontrolle dazugehört setze ich voraus. In 
meinem Falle Tortoise SVN.

Wahrscheinlich ist der Weg, wie oben angesprochen, der beste. Ein 
config-File und den Rest dann über #defines in die einzelnen Module 
verlinken. So behält man sich immer eine gewisse Portabilität.

ulf

von Markus B. (Firma: Embedit Mikrocontrollertechnik) (_mb_)


Lesenswert?

Peter Dannegger schrieb:
> Die *.h der einzelnen Peripherietreiber enthalten ja nur die
> Funktionsnamen und globalen Variablen, da muß nichts geändert werden.

Naja, beim UART will man vielleicht die Baudrate und Buffer definieren, 
beim LC Display will man festlegen, auf welchem Port es liegt und ob es 
ein HD44780 oder KS0073 ist usw. Man kann das mit verschiedenen Ansätzen 
lösen, aber direkt in die lcd.h oder uart.h gehört das IMHO nicht

Eine universelle und in allen Fällen 100% funktionierende Lösung wird es 
nicht geben. Es gibt immer Ausnahmen, die das beste Konzept aushebeln.


> Bei mancher eingebauten Peripherie der AVRs (z.B. Timer, UART) hat Atmel
> aber einen ziemlichen Wildwuchs veranstaltet, was die Funktionen und die
> Namensvergabe betrifft. Die muß man an nen anderen AVR per Define
> anpassen, soweit möglich.

In der Tat, da sagst du was wahres.

von Oliver (Gast)


Lesenswert?

>Peter Dannegger schrieb:
>> Die *.h der einzelnen Peripherietreiber enthalten ja nur die
>> Funktionsnamen und globalen Variablen, da muß nichts geändert werden.

Zu jedem Modul gibt es die Interfacedefinition und die 
projektspezifischen (Hardware-)Anpassungen. Die lassen sich 
selbstverständlich in unterschiedlichen Dateien unterbringen, z.B. lcd.h 
für das Interface und lcd_config.h für die Konfiguration.

Ob man jetzt den Inhalt aller *_config.h-Dateien in einer Datei 
zusammenfasst, oder die alle einzeln includet, ist Geschmackssache. Ich 
sehe keinen Vorteil in nicht-projektspezifischen zentralen Dateien, die 
von mehreren anderen, untereinander völlig unabhängigen, Dateien 
abhängen.

Oliver

von Martin (Gast)


Lesenswert?

Man kann die Hardwarezugriffe von der Software über Funktionszeiger 
entkoppeln, so wie das zum Beispiel für die Funktion printf gelöst ist. 
Konkret bedeutet das, dass eine Funktion, die die Bit-Zugriffe 
verwaltet, projektspezifisch implementiert wird und dann der allgemeinen 
Klasse zur Ansteuerung von LCDs übergeben wird.

von Markus B. (Firma: Embedit Mikrocontrollertechnik) (_mb_)


Lesenswert?

Martin schrieb:
> Man kann die Hardwarezugriffe von der Software über Funktionszeiger
> entkoppeln,
Kann man auch. Habe ich bei den "write()" Funktionen in der utils.c so 
gemacht.
http://www.wiki.elektronik-projekt.de/mikrocontroller/avr/avr-gcc-library/utils_library#funktionen


Ich empfinde das aber ansonsten eher als unnötigen Overhead. Man kann 
auch darüber streiten ob man die Baudrate für den UART in einer Config 
Datei ablegt oder ob man ihn der init() Routine als Parameter übergibt. 
So hatte ich das ursprünglich. Aber ich denke, das sind unnötig 
verschwendete Bytes, da die meisten Programmierer die Baudrate nicht zur 
Laufzeit ändern.

von Simon K. (simon) Benutzerseite


Lesenswert?

Martin schrieb:
> Man kann die Hardwarezugriffe von der Software über Funktionszeiger
> entkoppeln, so wie das zum Beispiel für die Funktion printf gelöst ist.
> Konkret bedeutet das, dass eine Funktion, die die Bit-Zugriffe
> verwaltet, projektspezifisch implementiert wird und dann der allgemeinen
> Klasse zur Ansteuerung von LCDs übergeben wird.

Aber wenn man die Hardwarezugriffe über Defines entkoppelt hat man nicht 
den (schon angesprochenen) Programmoverhead, was ich für ein sehr 
wichtiges Argument halte.

Ich persönlich habe zu jedem Modul eine eigene Konfigurationsdatei. Also 
ModulConfig.h. Modul.h enthält nur alle Definitionen, die das umliegende 
Programm nutzt. Die Konfiguration (also logisch gesehen Informationen 
die von dem umliegenden Programm zurück in das Modul fließen) stehen 
dann in der Config Datei.

von Philipp B. (philipp_burch)


Angehängte Dateien:

Lesenswert?

Also ich mache für das ganze hardwareabhängige Zeugs jeweils ein 
init.c/init.h-Paar, welches einerseits die hardwareabhängigen 
Initialisierungen der Peripherie vornimmt und andererseits Makros zur 
unabhängigen Registerverwendung bereit stellt. So eine Headerdatei sieht 
dann etwa so wie im Anhang aus.
Ausserdem halte ich für jedes Projekt eine Kopie aller Dateien im 
Projektordner (Ausser der Standardlib natürlich), mit denen sich das 
Programm kompilieren lässt. Wirklich universell lassen sich solche 
Module ja ohnehin nur mit grossem Aufwand gestalten (Siehe auch 
avr-libc), das lohnt sich in meinen Augen nicht wirklich. Da kopiere ich 
lieber den letzten Stand in den Projektordner und nehme anschliessend 
die projektspezifischen Änderungen vor.

von Johann L. (gjlayde) Benutzerseite


Angehängte Dateien:

Lesenswert?

Asterix-007 schrieb:
> Moin Männers,
>
> Ich habe verschiede Bibliotheken (SPI, LCD, USART.... )
> Dort werden spezifische Register und Portsettings genutzt.

Zunächst mal um einer allgemeinen Verwirrung entgegenzuwirken:
Eine "Bibliothek" im Dunstkreis von C/C++ ist etwas, wogegen man linkt, 
also zB etwas mit der Endung .a, .so, .dll.

Ich vermute mal, hier handelt es sich nicht im eine Bibliothek in diesem 
Sinne (wie die libc), sondern um ein Archiv mit C-Quellen, die nicht zu 
einer Bibliothek kompiliert werden, gegen welche der Applikations-Code 
dann gelinkt wird.

> Wenn ich nun in verschiedenen Projekten diese Dateien einbinde, habe ich
> früher oder später Spaghetti-Code, zumindest was die Konfiguration der
> Register und Ports betrifft.
>
> Wie löst ihr das? Einfach in das main-file alle Registersettings hinein
> oder bindet ihr ein separates (z.B.) *.cfg-file mit ein.
>
> Für ein paar Vorschläge wäre ich sehr dankbar!

Mach ich am mehrern Fronten:

Ports: In jedem Projekt gibt es eine Zentrale für die Port-Vergabe, wie 
beschrieben in
   Beitrag "Re: Registernamen/Variablennamen erstellen"

Module wie für den USART sind in einem Quell-Archiv. Aus diesem Archiv 
werden zur Build-Zeit die benötigten Dateien ins Projekt kopiert.
Vom make-Prozess ist das etwas komplizierter, aber es vermeidet, daß man 
irgendwann zig Kopien der gleichen Quelle irgendwo rumgammeln hat. Mit 
"make clean" werden diese lokalen Quellen übrigens entfernt, da sie ja 
erstellt werden (nämlich durch Kopieren aus dem Quellarchiv).

In dieses Quellarchiv kommt nur Zeug, das sich über mehrer Projekte hin 
bewährt hat und einen hinreichenden Grad an Erprobtheit und 
Allgemeingültigkeit erlangt hat.

Wenn ich was austesten will, kann ich lokale Änderungen machen, und weil 
die Änderung neuer ist als das Original, wird sie beim make nicht 
überbügelt. Inkompatible Änderungen im Archiv aufgrund von Erweiterungen 
hab ich idR nie, die Erweiterungen sind in 98% der Fälle kompatibel mit 
alten Benutzungen.

Das Kopieren ist aus aus folgendem Grund notwendig: Alle meine 
Anwendungen haben asynchrone Countdown-Zähler, die eine SW-Zeitbasis für 
die Anwendung machen (LED-Blinken, DCF-Modul abfragen, Morse-Zeichen 
ausgeben, Bilschirmschoner aktivieren, ...). Die C-Quelle ist generisch 
gehalten und im Quellarchiv. Sie includet einen Header aus dem 
Projektverzeichnis, in dem nur festgelegt wird, welche Zähler eine 
Anwendung braucht.

Andere Konfigurationen mach ich per Defines bzw Makefile, zB gibt es für 
die Größe einer UART-FIFO ein Define UART_USE_OUTFIFO. Wenn dieses Makro 
definiert ist und einen Wert größer 0 hat, dann verwendet die uart.c die 
fifo.c und macht die Ausgabe über eine Ausgabe-FIFO der angegebenen 
Größe; ansonsten ist die Ausgabe ungepuffert.

Innderhalb der uart.h wird per "#if defined" et al. zwischen den 
Derivaten unterschieden, die ich verwende.

Was Timer angeht, so ist die AVR-Architektur da zu inhomogen und die 
ANwendungsfälle zu individuell, um gescheit und effizient von der 
Hardware abstrahieren zu können, etwa um eine phasenkorrekte PWM zu 
implementieren. Das mach ich also im Prokejt, indem ich wirklich die 
Quelle von einem Muster übernehme und anpasse. PWM verwende ich nur 
selten, ansonsten hätt ich vielleicht mal den Versuch gemacht, die 
analog zu UART zu abstrahieren...

Eine weitere Technik könnte sein, sich Bibliotheken für unterschiedliche 
Derivate bzw. Derivat-Familien zu erstellen. Das würde dann analog 
funktionieren wie beim Linken mit avr-gcc, wo abhängig vom µC gegen die 
passende Lib gelinkt wird: Bei einem ATmega64 wird gegen ne andere 
Ausprägung der Lib gelinkt als bei einem ATtiny24.

Aber zur Erstellung solcher (Multi-)Libs fehlt mir eben der Nerv. 
Ausserdem ist der Nachteil, daß dort keine Defines mehr wirken können, 
weil ne Lib ja schon kompiliert ist. Gerade bei Port-Zugriffen würde das 
zu ungünstigem Code führen, wenn man diese Zugriffe indirekt machen 
würde. Anderes Beispiel ist die Baudrate beim UART: In einer Lib müsste 
man wirklich anfangen und zur Laufzeit zu dividieren, während bei 
Verwendung einer Quelle (woimmer die auch herkommt) der Präprozessor die 
Werte für die Baud-SFRs berechnen kann, wenn die Baudrate zur 
Compilezeit bekannt ist.

In kleinem meiner Projekte wird indirekt auf Ports oder andere SFR 
zugegriffen -- schlicht aus Gründen der Effizient: der Code würde 
langsam und breit werden, und vor allem ist ein indirekter SFR-Zugriff 
nicht mehr atomar, so daß man global Interrupts sperren müsste bei 
jedem solchen Zugriff, um das wasserdicht zu haben!

Inzwischen hab ich einige Projekte so weit verallgemeinert, daß ich sie 
auch für einen PC erzeugen kann, um Analysen zu machen. Ich lass also 
die Applikation nicht aufm AVR laufen, sondern für den PC ist's nur ne 
Build-Variante, indem Zeug wie <avr/pgmspace.h> PC-verdaulich wird. Ein 
Simulator scheidet aufgrund der entstehenden Datenflut aus. Auf diese 
Weise hab ich zB die angehängte Grafik erzeugt. Der identische Code 
(Snake on Scope) läuft auf AVR und PC, nur daß andere Hooks verwendet 
werden, um einen Pixel zu setzen und nicht-standard-Zeug wie PROGMEM 
parametrisiert ist.

'n Haufen Prosa, vielleicht sind n paar Anregungen dabei...

Johann

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.