Hallo zusammen, ich habe hier eine Frage zur Projektorganisation/Speicherung der SW-Teile. Ich weiß nicht, wie ich das in der titelzeile ausdrücken soll. Ich habe eine Directory, unter der ich für jedes meiner kleinen (Bastel-)Projekte Subdirektoreies angelegt habe, unter denen dann der jeweilige Projektbaum (gemäß Atmel Studio 6.2) zu finden ist. Viele der einzelnen Sourcefiles, z.B. für die TFT-Ansteuerung, z.B. TFT.C, TFT.H werden in vielen meiner Projekte verwendet. Nun möchte ich diese "Standardroutinen" unter einer getrennten directory, z.B. mit Namen "pool", ablegen. Soweit, sogut, wenn ich eine solche Datzei nun im Studio als Sourfce hinzufüge, damit ich die Datei ggf. ändern/erweitern kann Nur speichert Atmel Studio diese geänderte Datei nicht unter "pool" zurück sondern unter "src" im jeweiligen Projekt. Nun ja, ich könnte diese Dateien in "main" o.ä. includieren, nur dann wäre ein einfaches Ändern der Datei nicht möglich und müßte extern erfolgen. Kennt jemand für mein "Problem" einen Lösungsweg? Oder bin ich nur zu blöde, kann ja auch sein.. :-)
Hanns-Jürgen M. schrieb: > Kennt jemand für mein "Problem" einen Lösungsweg? Meines Wissens nach kann man in Atmel Studio Sourcen direkt als Datei oder "gepointert" als Datei-Link im Projet einbinden. Mit Datei-Link wärst du womöglich da wo du hin willst? Hanns-Jürgen M. schrieb: > (gemäß Atmel Studio 6.2) Auf jeden Fall würde ich von dem buggy und zähem 6.2 auf 7.x wechseln.
@Och nöö Danke für den Tip, ich werde mal suchen. Update auf Studio Vers. 7. hatte ich mal durchgeführt, aber das ist leider auf meinem anderen unter XP laufenden Rechner nicht möglich. Und denn kann ich nicht auf 7 updaten, da Win7 für wichtige ältere Hardware keine Treiber hat. Und somit bleibt aus Kompatiblitätsgründen nur die Version 6.2 übrig. Bugs sind mir dort bislang noch nicht aufgefallen, nur Probs mit der Toolchain, aber auch diese ließen sich umschiffen.
Hanns-Jürgen M. schrieb: > leider auf meinem anderen unter XP laufenden Rechner nicht möglich. Ist mir auch so ergangen. Seitdem liegt Atmel Studio nutzlos "in der Ecke", auch wenn ich mittlerweile auch Win 7 habe. Ich wollte Atmel-ARM machen, aber nachdem Atmel einem die Gangart in der Entwicklungsumgebung nicht leicht machte (man denke nur an das ganze .NET Gerödele) ist der Sprung zu STM32 gaaaaanz schmerzlos vonstatten gegangen - dank der vielseitigen Möglich- keiten einer Entwicklungsumgebung.
Hallo Hanns-Jürgen M., das Problem kenn ich auch. Alles, was in einem Projekt verwendet wird, ist dort auch im Verzeichnis gespeichert. Einerseits ist das gut, aber ... wenn dann man eine Library ändert, muss man diese wieder extra speichern für weitere Verwendung. Ich hatte mal das ASF-Framework verwendet, da war das dann genauso. Vor Jahren hatte ich dann AVR32 uCs mit dem AVR32-Studio verwendet, was auch gut lief, aber da war das Speichern ähnlich. Das AVR32-Studio wird schon seit langem nicht mehr unterstützt. Und dann musste ich zwangsweise auf Atmel Studio umsteigen, was auf älteren PCs dank .NET auch nicht gut läuft. Also ich finde persönlich die IDE auch echt schrecklich, obwohl das ASF Framework durchaus brauchbar ist, wenn man verstanden hat, wie diese aufgebaut ist und genutzt wird. Da bleibt mal abzuwarten wie sich das jetzt mit Microship ändern wird. Ich habe auch die uC-Familie und die IDE gewechselt.
geht das nicht mit .include C:\Pool\...
Thomas O. schrieb: > geht das nicht mit .include C:\Pool\... Ja, das geht, aber genau das wollte ich verhindern. Ich habe da ggf. eine Lösung gefunden, muß das aber noch austestetn, ob es grundsätzlich brauchbar ist..
Hanns-Jürgen M. schrieb: > Nun möchte ich > diese "Standardroutinen" unter einer getrennten directory, z.B. mit > Namen "pool", ablegen. Laß so etwas lieber sein. Deine Quelltexte mögen ja von einem zum anderen Projekt so ziemlich gleich sein, insbesondere dann, wenn du immer nur den gleichen Chip-Typ verwendest, aber dennoch: Laß alles, was du nicht mit #include <xxxxx> einbindest, in deinem Projekt-Verzeichnis, denn dadurch hältst du dein Projekt in sich konsistent. Das ist wichtiger, als du es derzeit vielleicht meinst. Ich selber bin ein großer Freund von weitgehend flach organisierten Projekt-Verzeichnissen, also derart, daß alles, was zum eigentlichen Übersetzen benötigt wird, in einem einzigen Verzeichnis drinsteht. Speicherplatz ist bei heutigen Festplatten ja kein Thema mehr. W.S.
@W.S. Nun, Deine Begründung ist richtig... in Teilen. Ich nehme mal ein Beispiel, Das File DCF.C diene zur DCF-Decodierung und wird in fast allen meinen Projekten verwendet. Nun habe ich kürzlich die Routinen etwas verändert, ohne dabei die Schnittstellen zu verändern. Wenn ich nun dieses File nicht im Pool habe, dann muß ich es manuell in jede Projekt-Directory reinkopieren. Und das ist nicht nur mühseelig, sondern könnte, falls eine Projektdirectory übersehen wird, zu bösen zeitrauzbenden Bugs führen. Ein anders Beispiel wäre eine Font-Datei, die ich ggf. erweitert habe.
du könntest das AVR-Studio über eine Batchdatei starten in der du vor dem eigentlichen Start deine Pool Dateien mit den anderen Projektordnern vergleichst und ggf. aktualisieren läßt. glaube das war robocopy mit dem man solche Syncronisierungsaufgaben per Kommandozeilenaufruf (bzw. dann eben in die Batchdatei aufnehem) erledigen kann.
@Thomas O et altera von einem Start mittels einer Batchdatei halte ichm, unter Windoof, eher gar nichts. Falls da etwas schiefläuft und Atmel Studio beendet wird, ohnen die Dateien zurück zu kopieren ist alles am A...., wenn man dann nicht manuell eingreift. Unter DOS verwende ich Batch-Dateien, dort ist es eher kein Problem. (Alter IAR Compiler für HC11 und 6809 SW, ja ja, ich weiß, Schnee von vorvorgestern) Ich habe nun eine einigermaßen brauchbare Lösung gefunden, die auf #includes beruht. Ich wollte das eigentlich nicht, aber okay. Im ersten Schritt lege ich in der directory (Beisoielname) "POOL" die Standardroutinen (.c und .h Files) ab. Projektbezogen sind sie in der Projektdirectory nicht mehr zu finden, auch aus dem Makefile werden sie entfernt. Dazu lege ich projektbezogen einen c-Datei POOLFILES.C und eine Headerdartei POOLFILES.C an. Die POOLFILES.C wird im Makefile als zu kompilierende und zu linkende datei eingetragen. Die POOLFILES.C enthält nun projektbezogen die benötigten C-Dateien als INCLUDES:
1 | ////////////////////////////////////////////////////////////////////////
|
2 | //
|
3 | // poolfiles.c
|
4 | // ////////////////////////////////////////////////////////////////////////
|
5 | // // Hier sind alle Sourcecodefiles aus dem Pool
|
6 | // includiert, die fuer das projekt aus dem Pool genommen
|
7 | // werdden
|
8 | // ////////////////////////////////////////////////////////////////////////
|
9 | // je nach Bedarf:
|
10 | #include <stdio.h> |
11 | #include <avr/pgmspace.h> |
12 | #include <avr/wdt.h> |
13 | #include <avr/interrupt.h> |
14 | |
15 | // Die im Projekt angelegte Datei mit den globalen
|
16 | // Definitionen und Deklarationen
|
17 | #include "c_global.h" |
18 | |
19 | |
20 | // Die Sourcedateien
|
21 | #include "../pool/c_dcf.c" |
22 | #include "../pool/c_clock.c" |
23 | #include "../pool/c_tft.c" |
24 | #include "../pool/c_tft_fonts_1.c" |
25 | |
26 | //***************************************************************
|
27 | //*
|
28 | //* end of: POOLFILES.C
|
29 | //*
|
30 | //***************************************************************
|
Desweiteren wird eine Headerdaei POLLFILES.H angelegt, die die im Projekt benötigten Headerdateien der Pool-Sources beinhaltet. Diese Datei ind in die projekt-Surcefiles zu includieren.
1 | ////////////////////////////////////////////////////////////////////////
|
2 | //
|
3 | // poolfiles.h
|
4 | // ////////////////////////////////////////////////////////////////////////
|
5 | //
|
6 | // Hier sind alle Headerfiles aus dem Pool
|
7 | // includiert, die fuer das Projekt aus dem Pool genommen
|
8 | // werdden
|
9 | // ////////////////////////////////////////////////////////////////////////
|
10 | // je nach Bedarf:
|
11 | #ifndef _POOLFILES_H
|
12 | #define _POOLFILES_H
|
13 | |
14 | #include "c_global.h" |
15 | |
16 | #include "../pool/c_dcf.h" |
17 | #include "../pool/c_clock.h" |
18 | #include "../pool/c_tft.h" |
19 | #include "../pool/c_tft_fonts_1.h" |
20 | |
21 | |
22 | #endif
|
23 | |
24 | //////////////////////////////////////////////////////////
|
25 | //
|
26 | // End of: POOLFILES.h
|
27 | //
|
28 | //////////////////////////////////////////////////////////
|
Vlt/wahrscheinlich gibt es noch Verbesserungen, aber so funzt es zumindest.
Hanns-Jürgen M. schrieb: > Die POOLFILES.C enthält nun projektbezogen die benötigten C-Dateien als > INCLUDES: Man includiert eigentlich keine .c Dateien. Man kompiliert sie separat und linkt sie dazu.
Bernd K. schrieb: > Hanns-Jürgen M. schrieb: >> Die POOLFILES.C enthält nun projektbezogen die benötigten C-Dateien als >> INCLUDES: > > Man includiert eigentlich keine .c Dateien. Man kompiliert sie separat > und linkt sie dazu. Das ist mir bekannt, aber solange die "POOL"-Routinen nicht nicht endgültig fixiert sind und gelegentliche Ergänzungen oder Korrekturen macht das weniger Sinn. Außerdem sind einige Sourcecodes dabei, die variabel, d.h. "bedingt" kompiliert werden, und das geht IMHO mit Libs eher weniger. ????
Hanns-Jürgen M. schrieb: > Bernd K. schrieb: >> Hanns-Jürgen M. schrieb: >>> Die POOLFILES.C enthält nun projektbezogen die benötigten C-Dateien als >>> INCLUDES: >> >> Man includiert eigentlich keine .c Dateien. Man kompiliert sie separat >> und linkt sie dazu. > > Das ist mir bekannt, aber solange die "POOL"-Routinen nicht nicht > endgültig fixiert sind und gelegentliche Ergänzungen oder Korrekturen > macht das weniger Sinn. wieso? Du musst doch nur dafür sorgen daß die für die IDE als zum Projekt zugehörig betrachtet werden, egal wo sie liegen. Wenn die IDE das partout nicht akzeptiert daß das in einem anderen Ordner ist dann mach doch einfach Symlinks oder Hardlinks zu den entsprechenden Dateien, dann sehen die für die IDE und für das Buildsystem so aus als lägen die direkt in Deinem Projekt, obwohl sie eigentlich woanders liegen. Alter Trick, haben wir schon vor 30 Jahren so gemacht und Windows kann das jetzt neuerdings (endlich) auch. > Außerdem sind einige Sourcecodes dabei, die variabel, d.h. "bedingt" > kompiliert werden, und das geht IMHO mit Libs eher weniger. ???? Ich spreche nicht von Libraries. Ich spreche von einzeln zu kompilierenden c Dateien. Jede Datei des Projekts wird einzeln für sich kompiliert zu jeweils einer .o Datei und am Schluss werden alle .o Dateien zu einer .elf gelinkt. Und aus dem .elf wird dann in einem weiteren Schritt noch das .hex zum Flashen erzeugt.
:
Bearbeitet durch User
Die wirklich saubere Lösung wäre allerdings diesen "Pool" in ein eigenes git repo zu stecken und in den Repositories der jeweiligen Anwendungen einen Klon dieses Repositories als git submodule einzubinden, dann kann man es bei einzelnen Projekten auch auf eine bestimmte Revision festnageln falls neuere Versionen der Pool-Dateien für ein bestimmtes Projekt inkompatibel geworden sind.
:
Bearbeitet durch User
Eine andere Möglichkeit ist, "Standardroutinen" in eine eigene Bibliothek zu packen, die dann zu den Projekten dazugelinkt wird. So mache ich das zumindest. Aus Fonts, CRC-Tabellen etc. erstelle ich Assembler-Sourcen, die praktisch nur aus .byte Zeilen bestehen. Wenn ich jetzt irgendwo (z.B) glcd_text("Hello World"); aufrufe, dann sorgt letztendlich der Linker dafür, dass die Tabelle automatisch mit in den Code wandert.
Joerg W. schrieb: > Eine andere Möglichkeit ist, "Standardroutinen" in eine eigene > Bibliothek zu packen, die dann zu den Projekten dazugelinkt wird. Das ist leider nicht immer so einfach bei µC-Projekten. Beispiel: lcd.c:
1 | #include "lcd-config.h" |
In lcd-config.h wird dann die Verkabelung des LCDs definiert, welche durchaus von Projekt zu Projekt verschieden sein kann. Da nützt mir ein bereits kompiliertes lcd.o oder ein lib-lcd.a herzlich wenig. Ich habe auch die Erfahrung gemacht, dass Kopieren des Sources suboptimal ist. Baut man im Verlaufe des Projektes B eine Verbesserung in lcd.c ein, hat Projekt A erstmal nichts davon. Deshalb arbeite ich so, dass ich ein Verzeichnis ../lib benutze, in welchem der wiederverwendbare Code als Quelltext landet. In den meisten IDEs kann man auch Quellcode außerhalb des Projektordners hinzufügen. Wenn das nicht geht, hilft zur Not der Include-Trick. Aber schön ist anders.
Frank M. schrieb: > Ich habe auch die Erfahrung gemacht, dass Kopieren des Sources > suboptimal ist. Baut man im Verlaufe des Projektes B eine Verbesserung > in lcd.c ein, hat Projekt A erstmal nichts davon. Doch, es hat sehr viel davon, nämlich daß es immer noch läuft. Ich speichere daher allen Lib-Code immer lokal im jeweiligen Projekt. Ansonsten passiert es sehr leicht, wenn man alten Code anfassen muß, daß nichts mehr funktioniert, weil die Lib inzwischen "verbessert" wurde und damit inkompatibel zu alten Projekten ist.
Bernd K. schrieb: > Die wirklich saubere Lösung wäre allerdings diesen "Pool" in ein eigenes > git repo zu stecken und in den Repositories der jeweiligen Anwendungen > einen Klon dieses Repositories als git submodule einzubinden, dann > kann man es bei einzelnen Projekten auch auf eine bestimmte Revision > festnageln falls neuere Versionen der Pool-Dateien für ein bestimmtes > Projekt inkompatibel geworden sind. Danke für den Hinweis. Nur ich habe davon wirklich keine Ahnung. Ich habe jetzt mal nach "GIT PEPO" gegoogelt, um überhaupt zu erfahren, was das ist.. Ich meine, daß dies auch der richtige Weg wäre, aber ich betreibe alles nur noch als Hobby (ich bin auch ursprünglich kein SW-Mann) und scheue mich (noch) davor, mich in dieses neue Thema einzuarbeiten. Ob das dann überhaupt mit ATMEL Studio 6.2 unter Windoof zusammenfunzt, weiß ich nicht (??). Viele Grüße, Yogy
Peter D. schrieb: > Frank M. schrieb: >> Ich habe auch die Erfahrung gemacht, dass Kopieren des Sources >> suboptimal ist. Baut man im Verlaufe des Projektes B eine Verbesserung >> in lcd.c ein, hat Projekt A erstmal nichts davon. > > Doch, es hat sehr viel davon, nämlich daß es immer noch läuft. > Ich speichere daher allen Lib-Code immer lokal im jeweiligen Projekt. > Ansonsten passiert es sehr leicht, wenn man alten Code anfassen muß, daß > nichts mehr funktioniert, weil die Lib inzwischen "verbessert" wurde und > damit inkompatibel zu alten Projekten ist. Auch irgendwie richtig. Ich für meinen teil sichere jedes Projekt, an dem ich arbeite, und künftig auch den POOL, täglich unter einem eigenen Namen, z.B. POOL_2018_03_19 auf dem Sicherungsmedium. Damit kann ich beleiebig zurückgehen. Der Hinweis vom UKW-Moderator bringt es auf den Punkt, warum ich sources einbinden will, ich hatte das unter dem Begrief "bedingte Kompilierung" zusammengefaßt.
Peter D. schrieb: > Ansonsten passiert es sehr leicht, wenn man alten Code anfassen muß, daß > nichts mehr funktioniert, weil die Lib inzwischen "verbessert" wurde und > damit inkompatibel zu alten Projekten ist. Das ist die typische Angst, das etwas bei einer Aenderung kaputt gehen koennte, weshalb man eher dazu neigt nichts zu aendern, obwohl man etwas aendern will/sollte. Diese Angst vergeht ganz schnell, wenn man ordentliche Softwaretests hat, angefangen bei Unittests. "Ich aender das nicht, weil dann was kaputt gehen koennte" ist nur eine Ausrede, um schlechten Code zu rechtfertigen. Und von einem Modul 23 verschiedene Versionen, an 65 verschiedenen Orten zu haben, macht das ganze nicht besser. Denn dann musst du in jedem Projekt immer wissen, warum sich Funktion X in diesem Projekt moeglicherweise leicht anders verhaelt, als in einem anderen Projekt. Mal ehrlich: Die Zeiten mit haendischem Kopieren und Umbenennen sind schon lange vorbei. Wer glaubt das dieses Vorgehen auch nur irgendwelche Vorteile gegenueber einer Versionsverwaltung wie Git, SVN, etc. hat, der moege mal bitte ueber seinen Tellerrand gucken. Faengt doch schon bei einem kleinen Bugfix an. Projekt auschecken, bug fixen, einchecken, und schon haben alle Projekte nach einer aktualisierung, die von diesem Projekt abhaengen, auch den Bugfix. Denselben Bug in 10 Projekten fixen zu muessen, nur weil man das immer alles von Hand hin und her kopiert, und dann vermutlich auch noch an 1 oder 2 Stellen das fixen vergessen... klar, kann man machen, hat am Ende aber nur Nachteile. Alleine die Moeglichkeit sehen zu koennen, wann man was wo geaendert hat. Ganz ohne das man da haendisch irgendeine Textdatei pflegen muss (was man doch nicht macht, wegen 'keine lust' oder 'mach ich spaeter'). Und anbieter wie z.B. GitLab https://about.gitlab.com/ kosten nicht mal was. Selbst private Repos sind kostenlos.
Die Verkabelung des LCD habe ich (für mich) dahingehend gelöst, dass sowohl die Datenleitungen als auch die Steuerleitungen jeweils innerhalb eines Ports liegen müssen. Bis jetzt bin ich damit gut zurechtgekommen. Bei der Initialisierung gebe ich die Portpins mit an, z.B: init_glcd128a(PORT_A,PORT_C,0,1,2,3); wobei das "a" hinter "glcd128" in dem Fall für ein monochromes 128x64 Display mit 2 Low-aktiven CS Leitungen steht. Dieser Teil ist eigentlich "dumm", denn er stellt nur einen 1K großen Framebuffer bereit. Mittels einer Routine "serve_glcd128a()", die in den Tick-Interrupt mit eingebunden wird, werden jeweils 8x8 Pixel zum LCD übertragen. Und die eigentlichen LCD-Routinen wie Zeichen ausgeben oder Linien zeichnen sind völlig unabhängig von der angeschlossenen Hardware, da sie nur auf den Framebuffer zugreifen. Und ganz unten drunter liegen generische PORT-IO Routinen, damit das Ganze auch controllerunabhängig ist. Wenn ich jetzt ein neues LCD mit exotischer Ansteuerung habe, dann schreibe ich nur den Lowlevel-Teil neu und bin fertig. Wenn dann alles funktioniert, wandert der Code mit in die Bibliothek und ist danach auch für alle anderen Controller verfügbar. Und mit einem Script erzeuge ich aus den ganzen C-Datein in der library eine Header-Datei mit den Funktionsprototypen.
Joerg W. schrieb: > Die Verkabelung des LCD habe ich (für mich) dahingehend gelöst, dass > sowohl die Datenleitungen als auch die Steuerleitungen jeweils innerhalb > eines Ports liegen müssen. Bis jetzt bin ich damit gut zurechtgekommen. > Bei der Initialisierung gebe ich die Portpins mit an, z.B: > Nun, wenn ich "normale" LCD-Module verwende, dann gibt es 2 x 16, 4 x 20 oder auch 1 x 16 Module, die ich je nach HW Seriell über SPI oder über I2C oder 4-bit parallel oder 8 bit parallel ansteuere. Und die Ports ändern sich jenachdem welche (Arduino-)HW ich verwende. Hierzu nutze ich #define-Anweisungen. Bei den TFTs ist es noch schlimmer, da muß noch zwischen den TFT-Controllern unterschieden werden. Aber es führen immer viele Wege nach Rom (oder so ähnlich). Yogy
> und schon haben alle Projekte nach einer > aktualisierung, die von diesem Projekt abhaengen, auch den Bugfix. Das kann einem auch zum Verhängnis werden, wenn nämlich das Programm selbst schon den Bugfix enthält. Wenn z.B. in der Bibliotheksroutine ein Vorzeichenfehler ist. Da die bisherigen Projekte ja laufen, hatte man dort halt das Ergebnis der Routine addiert statt subtrahiert. Mit der gefixten Routine werden sämtliche bestehenden Projekte, wenn man sie neu baut, nicht mehr richtig funktionieren. Natürlich nur, soweit sie die einst fehlerhafte Routine nutzen. Gut, mit Vorzeichen ist es mir noch nicht passiert, aber mit Endianess. Nachdem ich die Routine (wegen STM32) für Little-Endian gefixt hatte, funktionierten die Projekte für PowerPC µC nicht mehr, da die Big-Endian sind. Letztendlich habe ich die Routine wieder "zurückgestellt" und eine neue universelle erstellt, die etwas anders heisst und beide varianten richtig behandelt. Das ist auch die, welche weiter gepflegt wird und wenn ich ein altes Projekt mal wieder anfasse, stelle ich den Aufruf (wenn ich dran denke) gleich mit um. Ähnlich ist es auch, wenn man z.B. einen weiteren Parameter für die Routine braucht oder sich die Bedeutung eines Parameters ändert. Hier ist es meiner Meinung nach viel besser, eine neue Routine zu erstellen anstelle an der alten herumzustricken. Letztendlich sorgt ja der Linker dafür, dass nur das eingebunden wird, was auch gebraucht wird.
Kaj G. schrieb: > Diese Angst vergeht ganz schnell, wenn man ordentliche Softwaretests > hat, angefangen bei Unittests. Zeig mir mal deinen Unit-Test für die Ansteuerung eines LCD-Displays ;-) ich halte auch wenig davon, Korrekturen automatisiert auf Projekte zu verteilen. Ich mach das nur, wenn ich das jeweilige Projekt auch anfasse. Ansonsten kommen bei einem jahrealten projekt beim ersten SCM-Update viele Änderungen, und ich weiss erstmal nicht wie mir geschieht. Ich habe mir da (auch in anderen Bereichen) eine spezielle Methodik zurechtgelegt: Im SCM (Subversion in meinem Fall) gibt es einen speziellen bereich, der "common code" enthält. Dieser bereich ist für sich alleine weder compilier- noch lauffähig; aber über Skripte kann ich automatisiert prüfen, inwieweit der common-code mit dem aktuellen Projekt übereinstimmt (wobei es zwei Fälle gibt: common ist aktueller, oder Projekt ist aktueller, auf die man dann auch unterschiedlich reagieren will) Oft ist es so, dass ich in einem Projekt common-code erweitere/ändere/optimiere, dieser muss sich aber erst über ein paar Tage/Wochen/Monate "bewähren", und ich will auf keinen Fall dass dieser bereits in andere Projekte einfliesst.
Kaj G. schrieb: > Diese Angst vergeht ganz schnell, wenn man ordentliche Softwaretests > hat, angefangen bei Unittests. In der idealen Welt sieht für Dich alles so schön einfach aus. Aber selbst die großen Programmierbuden schleppen Bugs oft lange mit, ohne sie zu entdecken. Und wenn man sie dann entdeckt, kann niemand sagen, wie sich der Bugfix oder die Erweiterungen auf alten Code auswirkt. Ich hatte zu oft das Problem, daß alter Code dann nicht mehr läuft und davon schlichtweg die Nase voll. Genau deshalb bin ich auf lokale Kopien zurück gekehrt.
> Zeig mir mal deinen Unit-Test für die Ansteuerung eines LCD-Displays ;-) Ich war zwar nicht gemeint, aber ich lade mal meinen oder zumindest einen Teil davon hoch. Dazu gehört natürlich immer noch ein board.h in dem sämtliche Einstellungen für das konkrete Controllerboard stehen. Auf praktisch allen (Test-)Boards habe ich eine rote+grüne LED zwecks Debugging, hier würde z.B. die rote LED leuchten, wenn der Controller zuwenig RAM hat (würde z.B. beim ATMega8 oder kleineren HCS08 passieren). Letztendlich werden ein Quadrat, ein ausgefülltes Quadrat, zwei schräge Linien und ein invertierter Text angezeigt. Und nebenbei bewegt sich ein kleines Sprite durch die Gegend... Insgesamt habe ich derzeit 41 Tests und die müssen bei jeder neuen Bibliotheksversion zumindest ohne Fehler compilieren, mit externer Hardware teste ich eigentlich nur neue Bibliotheksfunktionen.
Peter D. schrieb: > Ich hatte zu oft das Problem, daß alter Code dann nicht mehr läuft und > davon schlichtweg die Nase voll. Genau deshalb bin ich auf lokale Kopien > zurück gekehrt. ... kenne ich auch und macht sinn für mich! an svm kommt man trotzdem nicht mehr vorbei, selbst im hobbybereich. weil auch hier gibt es oft viele projekte und zahllose sw module mit entsprechenden abhängigkeiten. ich nutze und empfehle mercurial mit gui TortoiseHg. das ist auch im context des explorer integriert und ansonsten einfach zu verstehen und zu benutzen. mt
Peter D. schrieb: > In der idealen Welt sieht für Dich alles so schön einfach aus. Nein tut es nicht. Peter D. schrieb: > Aber selbst die großen Programmierbuden schleppen Bugs oft lange mit, > ohne sie zu entdecken. Und wenn man sie dann entdeckt, kann niemand > sagen, wie sich der Bugfix oder die Erweiterungen auf alten Code > auswirkt. Richtig. Weil auch grosse Softwarefirmen das Testing viel zu oft straeflich vernachlaessigen, weil es ja Zeit kostet. Sollte man mal gegenrechnen was es kostet die Software ordentlich zu entwickeln und mit tests auszustatten (TDD/BDD) und was das Suchen und Beheben von Bugs kostet. Unittests sind nicht die Loesung auf alles, richtig. Wenn man aber Tests hat, dann ist die Angst etwas kaputt zu machen, weil man etwas aendert, deutlich geringer. Die Tests (sofern sie gut sind) sagen dir doch, ob nach der Aenderung noch alles so funktioniert wie vorher.
Kaj G. schrieb: > TDD/BDD Ja, eh. In der "großen" Welt. Hier gehts um uC. Wenn ich weiter oben gefragt hatte, wie ein Unit-Test eines LCD-Treibers aussieht, dann meinte ich primär automatisierte Tests. Un diese sind aufgrund der Hardware-Nähe eines uC halt bissel kompliziert aufzusetzen. Joerg W. schrieb: > mit externer Hardware teste ich eigentlich nur neue Bibliotheksfunktionen. Was die Unit Tests in der weiteren Evolution eigentlich ad absurdum führt.
Das muß nicht unbedingt sein. Denn wenn ich z.B. Funktionen wie einen neuen SPI-Flash oder CRC-Berechnung hinzufüge, muss ich nicht die ganzen anderen Funktionen testen, die z.B. nur von Port-I/O abhängig sind. Wenn ich natürlich an einer Funktion herumschraube, die relativ "tief" liegt, also z.B. Port-I/O, muss ich natürlich die darüberliegenden Funtionen testen. Anfangs wollte ich das eigentlich gar nicht so hierarchisch aufziehen, aber nachdem ich z.B. Port-I/O auf fast allen Plattformen in ASM realisiert habe, ist der Overhead gar nicht mehr so schlimm. Wichtiger sind mir die Tests allerdings bei neuen Controllerfamilien, weil ich damit schnell grundlegende Funktionalitäten testen und auch bewerten kann. Sog. Highlevel-Funktionen wie z.B. LCD oder SD-Karte dürfen bei mir dazu weder voneinander noch von der darunterliegenden Hardware abhängig sein (nur von darunter liegenden Bilbliotheksfunktionen). Sie liegen in einem zenralen Verzeichnis und werden nur per Symlink in die Sourceverzeichnisse der Bibliotheken eingebunden. Das erspart schon mal jede Menge Testaufwand, weil ich erstmal nur auf einer Plattform testen muss. Anders geht es auch gar nicht, denn ich betreibe das privat in meiner "Freizeit". Und die besteht u.a. aus der täglichen Pendelei mit der S-Bahn, wo größere Aufbauten eher unpraktisch sind.
Joerg W. schrieb: > aber nachdem ich z.B. Port-I/O auf fast allen Plattformen in > ASM realisiert habe, Um Gottes Willen, warum denn das? War Dir langweilig? Und lässt sich das manuelle ASM Gedöns dann überhaupt noch von der LTO vernünftig inlinen (nur für den Fall daß es Dir tatsächlich um Performance ging und nicht nur um den Spaß an der Tüftelarbeit)?
:
Bearbeitet durch User
Der erste Punkt, ich lerne dabei. Wie der Controller funktioniert, was der Compiler für ASM-Code generiert... Mein Ziel war, dass z.B. set_portpin_output(PORT_A,3) überall "gleich" funktioniert, ähnlich wie beim Arduino. Und das mit möglichst wenigen Befehlen. Und so ist die Konstante PORT_A z.B. beim AVR ein (char) Tabellenoffset, der zeigt, wo die Adressen von PORTA,DDRA und PINA liegen, beim STM32 (unsigned long) die absolute Adresse von GPIOA. Außerdem kann man durch geschicktes Verschieben und Maskieren des Wertes auch gleich die notwendige Maske für RCC_APB2ENR berechnen. Die PowerPC Controller dagegen haben für jeden Pin ein extra Konfigurationsregister, dort habe ich dann auch keinen ASM-Code verwendet. Hier ist die Konstante PORT_A einfach nur ein Offset (Port-Nr. * 16) und der C-Code für die Funktion lautet in dem Fall
1 | void set_portpin_output(int port, int pin) |
2 | {
|
3 | SIU.PCR[port+pin].REG=0x204; |
4 | }
|
Das ist ein Aufwand, den man sich aber nur einmal machem muss. LTO funktioniert meines Wissens nach nicht oder nur eingeschränkt mit dazugelinkten Bibliotheken.
Joerg W. schrieb: > Mein Ziel war, dass z.B. set_portpin_output(PORT_A,3) überall "gleich" > funktioniert, ähnlich wie beim Arduino. Und das mit möglichst wenigen > Befehlen. Ich hab mir für jeden der handvoll Controller mit denen ich hauptsächlich unterwegs bin jeweils ein Python script geschrieben das mir ein gpio.h generiert. Meine Pins werden dann zum Beispiel so definiert: Inhalt von gpio.py
1 | #!/usr/bin/python3
|
2 | |
3 | from gpio_mkv11 import pin, Port, Init, generate, define |
4 | |
5 | i2c_mux = Init.ALT7 |
6 | |
7 | pin('AMP_C0', Port.D, 6, Init.OUT_LOW) |
8 | pin('AMP_C1', Port.D, 4, Init.OUT_LOW) |
9 | pin('AMP_C2', Port.D, 7, Init.OUT_LOW) |
10 | pin('AMP_C3', Port.D, 5, Init.OUT_LOW) |
11 | |
12 | pin('AMP_ERR_C0', Port.C, 4, Init.IN_PULL_UP) |
13 | pin('AMP_ERR_C1', Port.B, 0, Init.IN_PULL_UP) |
14 | pin('AMP_ERR_C2', Port.C, 5, Init.IN_PULL_UP) |
15 | pin('AMP_ERR_C3', Port.C, 3, Init.IN_PULL_UP) |
16 | |
17 | pin('LED_AMP_C0', Port.C, 2, Init.OUT_LOW) |
18 | pin('LED_AMP_C1', Port.C, 1, Init.OUT_LOW) |
19 | pin('LED_AMP_C2', Port.B, 1, Init.OUT_LOW) |
20 | |
21 | pin('LED_CAN_C0', Port.E, 17, Init.OUT_LOW) |
22 | pin('LED_CAN_C1', Port.E, 18, Init.OUT_LOW) |
23 | pin('LED_CAN_C2', Port.E, 19, Init.OUT_LOW) |
24 | |
25 | pin('I2C_SCL', Port.C, 6, i2c_mux) |
26 | pin('I2C_SDA', Port.C, 7, i2c_mux) |
27 | |
28 | pin('CAN_TX', Port.E, 24, Init.ALT2) |
29 | pin('CAN_RX', Port.E, 25, Init.ALT2) |
30 | |
31 | define('I2C_PIN_MUX', i2c_mux) |
32 | |
33 | generate('gpio.h') |
und das erzeugt dann sowas:
1 | /**
|
2 | * generated by gpio.py - do not edit
|
3 | */
|
4 | |
5 | #ifndef GPIO_H_
|
6 | #define GPIO_H_
|
7 | |
8 | #include <fsl_device_registers.h> |
9 | #include <stdbool.h> |
10 | |
11 | |
12 | |
13 | #define AMP_C0_SHIFT (6)
|
14 | #define AMP_C0_MASK (1UL << AMP_C0_SHIFT)
|
15 | #define AMP_C0_PORT PORTD
|
16 | #define AMP_C0_GPIO GPIOD
|
17 | #define AMP_C0_FGPIO FGPIOD
|
18 | |
19 | static inline void AMP_C0_mux(const uint8_t altnum) { |
20 | AMP_C0_PORT->PCR[AMP_C0_SHIFT] = PORT_PCR_MUX(altnum); |
21 | }
|
22 | |
23 | static inline void AMP_C0_high(void) { |
24 | AMP_C0_FGPIO->PSOR = AMP_C0_MASK; |
25 | }
|
26 | |
27 | static inline void AMP_C0_low(void) { |
28 | AMP_C0_FGPIO->PCOR = AMP_C0_MASK; |
29 | }
|
30 | |
31 | static inline void AMP_C0_toggle(void) { |
32 | AMP_C0_FGPIO->PTOR = AMP_C0_MASK; |
33 | }
|
34 | |
35 | static inline void AMP_C0_as_output(void) { |
36 | AMP_C0_FGPIO->PDDR |= AMP_C0_MASK; |
37 | }
|
38 | |
39 | static inline void AMP_C0_as_input(void) { |
40 | AMP_C0_FGPIO->PDDR &= ~AMP_C0_MASK; |
41 | }
|
42 | |
43 | static inline bool AMP_C0_is_high(void) { |
44 | return AMP_C0_FGPIO->PDIR & AMP_C0_MASK; |
45 | }
|
46 | |
47 | static inline bool AMP_C0_is_low(void) { |
48 | return !(AMP_C0_FGPIO->PDIR & AMP_C0_MASK); |
49 | }
|
50 | |
51 | |
52 | // und so weiter für jeden
|
53 | // Pin das selbe wieder
|
54 | // [...] gekürzt
|
55 | |
56 | #define I2C_PIN_MUX 7
|
57 | |
58 | static inline void gpio_init(void) { |
59 | SIM->SCGC5 |= SIM_SCGC5_PORTD_MASK | SIM_SCGC5_PORTC_MASK | SIM_SCGC5_PORTB_MASK | SIM_SCGC5_PORTE_MASK; |
60 | PORTB->PCR[0] = 0x00000003; |
61 | PORTB->PCR[1] = 0x00000100; |
62 | PORTC->PCR[1] = 0x00000100; |
63 | PORTC->PCR[2] = 0x00000100; |
64 | PORTC->PCR[3] = 0x00000003; |
65 | PORTC->PCR[4] = 0x00000003; |
66 | PORTC->PCR[5] = 0x00000003; |
67 | PORTC->PCR[6] = 0x00000700; |
68 | PORTC->PCR[7] = 0x00000700; |
69 | PORTD->PCR[4] = 0x00000100; |
70 | PORTD->PCR[5] = 0x00000100; |
71 | PORTD->PCR[6] = 0x00000100; |
72 | PORTD->PCR[7] = 0x00000100; |
73 | PORTE->PCR[17] = 0x00000100; |
74 | PORTE->PCR[18] = 0x00000100; |
75 | PORTE->PCR[19] = 0x00000100; |
76 | PORTE->PCR[24] = 0x00000200; |
77 | PORTE->PCR[25] = 0x00000200; |
78 | FGPIOB->PDDR = 0b0000000000000000000000000000010; |
79 | FGPIOC->PDDR = 0b0000000000000000000000000000110; |
80 | FGPIOD->PDDR = 0b0000000000000000000000011110000; |
81 | FGPIOE->PDDR = 0b0000000000011100000000000000000; |
82 | }
|
Das ganze in mein Makefile eingebaut, sobald ich die .py ändere baut er ein neues .h gpio_mkv11 kann ich leider nicht rausrücken, die Rechte gehören mir nicht allein, ist aber auch nicht sonderlich aufwendig. Es enthält noch ein paar Tabellen um bei Bedarf auch defines für den ADC Kanal oder Timerkanal des pins auszuspucken oder generiert automatisch code um NMI abzuschalten wenn ich den entsprechenden Pin verwende und solche Kleinigkeiten. Nur so als Anregung. Alles was man nicht vernünftig als Makro hinschreiben kann lass ich mittlerweile mittels Python generieren. hat auch den Vorteil daß beim Debuggen echter Code in einer echten Quelldatei zur Verfügung steht.
:
Bearbeitet durch User
Es ist halt wie weiter oben schon mal geschrieben: Viele Wege führen nach Rom. Und auch für mich war der eine oder andere interessante Aspekt dabei.
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.