Forum: Compiler & IDEs Atmel Studio: "Standard" SW-Routinen aus einem Pool verwenden


von Hanns-Jürgen M. (yogy)


Lesenswert?

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

von Och nöö (Gast)


Lesenswert?

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.

von Hanns-Jürgen M. (yogy)


Lesenswert?

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

von Och nöö (Gast)


Lesenswert?

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.

von HKN (Gast)


Lesenswert?

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.

von Thomas (kosmos)


Lesenswert?

geht das nicht mit .include C:\Pool\...

von Hanns-Jürgen M. (yogy)


Lesenswert?

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

von W.S. (Gast)


Lesenswert?

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.

von Hanns-Jürgen M. (yogy)


Lesenswert?

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

von Thomas (kosmos)


Lesenswert?

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.

von Hanns-Jürgen M. (yogy)


Lesenswert?

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

von Bernd K. (prof7bit)


Lesenswert?

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.

von Hanns-Jürgen M. (yogy)


Lesenswert?

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

von Bernd K. (prof7bit)


Lesenswert?

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
von Bernd K. (prof7bit)


Lesenswert?

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
von Joerg W. (joergwolfram)


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Hanns-Jürgen M. (yogy)


Lesenswert?

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

von Hanns-Jürgen M. (yogy)


Lesenswert?

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.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

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.

von Joerg W. (joergwolfram)


Lesenswert?

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.

von Hanns-Jürgen M. (yogy)


Lesenswert?

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

von Joerg W. (joergwolfram)


Lesenswert?

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

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Joerg W. (joergwolfram)


Angehängte Dateien:

Lesenswert?

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

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

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

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

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.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

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.

von Joerg W. (joergwolfram)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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
von Joerg W. (joergwolfram)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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
von Joerg W. (joergwolfram)


Lesenswert?

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