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
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
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.