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
>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
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
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.
>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
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>
><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
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.
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
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
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.
>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
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.
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.
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.
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.
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