Hallo da!
Ich hab ja nun zunächst für meine ersten Tests CooCox benutzt,
allerdings kam es mir nach einigen Studen wie ein verprogrammiertes
Eclipse vor, bei dem man nicht mehr an wichtige Konfigurationssachen
kommt und das gerne mal abstürzt, zusammen mit lock-in-Problem, also
habe ich mir jetzt Eclipse für das ARM-Plugin eingerichtet, soweit so
gut, Debugging funktioniert schonmal wieder.
Aber ich muss mich jetzt wohl auch mal mit den Eigenheiten der Toolchain
und der µC-Plattform auseinandersetzen. Dazu habe ich einige Fragen,
vielleicht könnt ihr mir dabei helfen:
1. Im Standard-Zustand wird das Programm laut Linker-Skript in das Flash
geladen. Ist das eigentlich ein Problem wegen begrenzter Schreibzyklen?
Wenn ich die Dokumentation des STM richtig gelegen habe, dann haben
externe Bus-Teilnehmer (das gilt wohl auch für SWD und JTAG) nicht die
Möglichkeit, auf den über die BOOT-Pins an Adresse 0 gemappten Speicher
zuzugreifen. Das heißt, ich muss praktisch zwei verschiedene
Linker-Skripte vorhalten, wenn ich beim Debuggen in den Speicher laden
will statt in das Flash, oder?
2. Auf meinem Experimentier-Board ist ein zusätzlicher Flash-Baustein,
aber der kann ja eigentlich nur über den FSMC der µC angesprochen
werden, oder? Das heißt, ich kann da mit dem (JLink-)Programmierer nix
reinladen, sondern das muss per Software auf dem Gerät geschehen?
3. Ich habe mein Projekt für C++ eingerichtet, mir fiel auf, dass die
Objekt-Dateien sofort etwas größer wurde. Ich möchte nun aber eigentlich
gar keine speziellen C++-Features außer der Syntax, den Namespaces und
ganz wichtig den Templates verwenden. Gibt es eine Möglichkeit, die
Auswirkungen weiter zu minimieren?
4. Ich habe mir die STM-HAL-Bibliothek weiter angeschaut. Wenn man die
verwendet, dann sind die Control-Register-Zuweisung dermaßen
ineffizient, dass ich mich nicht traue das so zu verwenden. Ist der
Compiler gut genug, dass er Struktur-Zuweisungen mit Konstanten und dem
nachfolgenden Funktionsaufruf der HAL, wo dann aus den Feldern der
Struktur ein Wert zusammenge-odert wird, im optimierten Build zu einer
konstanten Zuweisung auf den Register vereinfachen kann, sodass da gar
nix gewesen ist? Ich habe jetzt zwar angefangen, mir für den FSMC eine
Template-Bibliothek zu schreiben, aber wenn ich das nun auch noch für
alle anderen Module machen muss, sitze ich da schnell wochenlang dran.
Okay, soweit erstmal, was mir gerade durch den Kopf geht, wär' toll,
wenn ihr mir weiterhelfen könntet.
Viele Grüße!
BastiDerBastler schrieb:> 3. Ich habe mein Projekt für C++ eingerichtet, mir fiel auf, dass die> Objekt-Dateien sofort etwas größer wurde.
Das ist kein Indikator. Du musst dir wenn schon die Größe des
tatsächlichen Codes anschauen (arm-none-eabi-readelf -S blabla.o). In
den Objektdateien im ELF sind jede Menge Metadaten abgelegt die nicht im
Flash landen.
> Ich möchte nun aber eigentlich> gar keine speziellen C++-Features außer der Syntax, den Namespaces und> ganz wichtig den Templates verwenden. Gibt es eine Möglichkeit, die> Auswirkungen weiter zu minimieren?
Siehe
http://www.mikrocontroller.net/articles/ARM_GCC#Code-Gr.C3.B6.C3.9Fe_optimierenBastiDerBastler schrieb:> Das heißt, ich muss praktisch zwei verschiedene> Linker-Skripte vorhalten, wenn ich beim Debuggen in den Speicher laden> will statt in das Flash, oder?
Das brauchst du so oder so. Schließlich tauchen die Adressen auch im
Code selber auf und die müssen je nach verwendetem Speicher entsprechend
sein. Außerdem ändert sich ja die RAM-Aufteilung...
BastiDerBastler schrieb:> Ist der> Compiler gut genug, dass er Struktur-Zuweisungen mit Konstanten und dem> nachfolgenden Funktionsaufruf der HAL, wo dann aus den Feldern der> Struktur ein Wert zusammenge-odert wird, im optimierten Build zu einer> konstanten Zuweisung auf den Register vereinfachen kann, sodass da gar> nix gewesen ist?
Prinzipiell ja, wenn er die Funktionen inlined, was er bei den
ST-Funktionen tendentiell eher nicht tut (kannst die ja alle mit
__attribute__((always_inline)) markieren...). Ja, die ST-Library ist
ineffizient, und die Gegenmaßnahme seitens ST ist nicht deren
Optimierung (dafür müsste man halt mal fähige IT-Leute einstellen),
sondern großen Flash-Speicher reinzupacken...
Aber wie immer gilt: Nicht spekulieren, sondern sich den generierten
Code ansehen...
Dankeschön für die Informationen.
Ich habe dann gestern danach RAM-Debugging umsetzen können (dafür musste
ich sogar den startup-code verändern O.O).
Mit der Größe meinte ich natürlich schon die Section-Größen, die der
Binary-Bauer da ausspuckt. Aber im Release-Build bin ich da jetzt knapp
unter 3kb, das muss wohl erstmal reichen.
Eine andere Sache. Ich habe jetzt die letzten zwei Tage einfach
spaßeshalber an einer Template-Bibliothek gearbeitet, die die STM-Header
zumindest ergänzen können soll. Besteht irgendwie bei anderen Interesse
an sowas? Folgend ein paar kleine Beispiele (achtung: wer nicht
template-verrückt ist, sollte lieber weglesen...). Ist natürlich alles
total frisch, nicht fertig und vielleicht gibts tausendmal bessere
Ansätze. Aber wenn es hier noch andere Template-Jünger gibt, könnte man
ja vielleicht irgendwie die Kräfte vereinen. Entschuldigt außerdem bitte
die breiten Zeilen, wenn ich für mich selbst kodiere, nutze ich den
Bildschirmplatz meist aus.
usingahb1enr=system_register<0x40023800+0x30,uint32,0x00100000,~bits::mask_from_bits<uint32,31,24,19,17,16,15,14,13,11>::value>;// core coupled memory is enabled by default
// error: static assertion failed: Field mask covers reserved bits.
45
46
usingviolation_field_value=stm::make_field_value<violation_field,3>;// Fehler: 3 passt nicht in 1 bit
47
// error: static assertion failed: Field value too wide for mask space.
48
49
fsmc::bcr<2>::templateassign_fields<violation_field_value>();// Fehler: bit 30 ist ein reserved bit
50
// error: static assertion failed: Trying to write to reserved register bits
51
52
fsmc::btr<2>::templateassign_fields<fsmc::control_fields::mtyp<2>>();// memory type hat nix im timing-register zu suchen!
53
// error: static assertion failed: Bad field values for this register
Die Register und deren Felder zu indizieren ist darauf aufbauend
eigentlich nur noch Fleißarbeit, aber Helferklassen für die einzelnen
Module erfordern natürlich Erfahrung mit ihnen und etwas Hirnschmalz.
... resultiert in 5 stores im Assembly, was Sinn ergibt, weil er in der
range 7-15 sowohl das low als auch das high alternate register
überdeckt!
Ich finde, so spart man doch erheblich an Zeilen ;)
Eine Library die so arbeitet gibt es schon irgendwo im Internet (weiß
nicht mehr wie sie hieß). Das Problem ist, dass so zur überall zur
Compiletime feststehen muss, welche Peripherieeinheit verwendet werden
soll:
BastiDerBastler schrieb:> fsmc::channel1<2>::
Hier kann man den Kanal nicht per Laufzeitparameter o.ä. ändern. Meiner
Meinung nach ist es essentiell dass man zB Pin-Namen an Funktionen per
Parameter übergeben können muss.
Hrmmm, ja, das sind zwei diametrale Ansätze, wenn ich Dich richtig
verstehe. Ich beschreibe die Hardware ja nun im Moment erstmal zur
Compile-Zeit.
Darauf aufbauend könnte man bestimmt Runtime-Typen erzeugen, die dann
eben vielleicht auch nur im Debug-Build Runtime-Checks machen usw...
Ich müsste mir mal überlegen, wie man das gut implementieren und
kombinieren kann, vielleicht hast Du auch Ideen, wie so etwas vernünftig
aussehen könnte.
Der Bedarf für beide Ansätze ist bestimmt da. Weil was zur Compile-Zeit
berechnet und gecheckt werden kann, sollte dort auch passieren. Und
dynamische Pin-Konfigurationen sind dann wohl auch wichtig (in die
Richtung hatte ich noch nicht so nachgedacht...). Es fragt sich nur, ob
mit dem einen überhaupt etwas für das andere gewonnen ist... hrmmm.
BastiDerBastler schrieb:> Ich müsste mir mal überlegen, wie man das gut implementieren und> kombinieren kann, vielleicht hast Du auch Ideen, wie so etwas vernünftig> aussehen könnte.
Habe ich, habe viel rumprobiert, scheitert daran dass die Compiler das
(noch) nicht vernünftig optimieren können :-/
BastiDerBastler schrieb:> Der Bedarf für beide Ansätze ist bestimmt da. Weil was zur Compile-Zeit> berechnet und gecheckt werden kann, sollte dort auch passieren. Und> dynamische Pin-Konfigurationen sind dann wohl auch wichtig (in die> Richtung hatte ich noch nicht so nachgedacht...).
Compiletime + Runtime lässt sich kombinieren indem der Compiletime-style
Code den Runtime Code verwendet aber noch static_assert Checks
hinzufügt.
Hrmmm, habe drüber geschaut, sein Code ist sehr anders aufgebaut und
"heavy" templates sehe ich da eigentlich keine ;)
Ich habe echt nicht vor Augen, wie so eine Runtime-Bibliothek aussähe,
sodass sie genug Informationen enthält, dass man darauf
Compile-Time-Checks auf dem Niveau, zu dem man nach meinem Ansatz
befähigt ist, und vor allem noch Felder auf Register verteilt werden
können, dass es zu minimalen Schreiboperationen kommt. Hrmmm. Hast Du
irgendwo noch Pseudo-Code rumliegen? Ich bin mit meiner Birne ja jetzt
lange in eine Richtung unterwegs gewesen, da brauch ich etwas Hilfe, um
wieder in eine andere Richtung zu beschleunigen.
Was ich auf jeden Fall im Moment sehe ist, dass in den Fällen, in denen
ich eine type_list verwende, weil dasselbe Feld in mehreren
gleichartigen Registern vorhanden ist, dann könnte ich im Vorbeilauf
auch einen Runtime-Typ generieren, der den Register als Variable
enthält.
Ebenso gleichartige Devices, die auch wieder auf mehrere Register mit
konstanten Offsets sind.
Wobei der Memory-Channel direkt ein schlechtes Beispiel wäre, weil die
einzelnen Channels ja nicht gleichförmig sind.
Bei den Timern sind ja auch wieder nicht alle gleich...
GPIO-Pins sind natürlich gleichförmig, das habe ich auch noch gar nicht
modelliert, was die letztendlichen Schreib&Lese-Operationen betrifft.
War der Compiler denn bei Deinen Ansätzen verifizierbar zu blöd, oder
gab es da vielleicht Regeln aufgrund derer er mehrere aufeinander
folgende load&store-Operationen nicht zusammenlegen darf (irgendwas mit
volatile-Regeln oder so).
BastiDerBastler schrieb:> Ich habe echt nicht vor Augen, wie so eine Runtime-Bibliothek aussähe,> sodass sie genug Informationen enthält, dass man darauf> Compile-Time-Checks auf dem Niveau, zu dem man nach meinem Ansatz> befähigt ist, und vor allem noch Felder auf Register verteilt werden> können, dass es zu minimalen Schreiboperationen kommt.
Ich hatte das hybrid-mäßig gemacht, wenn die Informationen nur zur
Laufzeit vorliegen wird gar nichts geprüft, wenn sie zur Compilezeit
vorliegen werden sie an ein template übergeben (und von da an den
regulären Code für die Laufzeit-Version) und überprüft.
> Hrmmm. Hast Du> irgendwo noch Pseudo-Code rumliegen? Ich bin mit meiner Birne ja jetzt> lange in eine Richtung unterwegs gewesen, da brauch ich etwas Hilfe, um> wieder in eine andere Richtung zu beschleunigen.
Nö den wollte ich auch noch nicht rausrücken...
Mein Hauptanliegen war es ein OOP-artiges API zu bieten das auch die
Zuordnung Parameter->Register übernimmt, s.d. der Usercode nichts mehr
von Registern sieht (wie bei ST). Wenn alle Parameter und ziele zur
Compilezeit bekannt sind, sollte der Compiler das zu wenigen Zugriffen
optimieren, und wenn nicht halt einen "dynamischen" Code generieren. Hat
leider nicht wirklich geklappt...
Ja hrmmm.
Also es war auch nicht mein Anliegen, dass ich noch die Register sehe
(will ich ja auch nicht), was ich gezeigt habe war ja zum größten Teil
nur, wie man in der Bibliothek intern jetzt die Hardware beschreibt (und
dabei als "meta"-Beiprodukt schon vor fehlerhafter Beschreibung
geschützt wird).
Das Beispiel mit dem rcc::enable ist ja, woraus es dann hinauslaufen
soll.
Mein Kopf rotiert jetzt, wie man den minimalen Code jetzt umsetzen
könnte mit generierten Runtime-Typen. Da müssen ja auf jeden Fall
überall Speicher-Offsets (egal ob jetzt konstante Offsets oder aus einer
Liste von möglichen Adressen) in den "optimalen" Code eingefügt werden,
damit man den das Ziel zur Laufzeit "platzieren" kann. Dann müsste der
Compiler nur schlau genug sein, dass wenn man das Objekt mit einem
konstanten Offset initialisiert, er alle Folge-Aufrufe auf diesen einen
initialen konstanten Wert bezieht.
Ich werde das auch einmal im kleineren Stil ausprobieren, interessiert
mich mal, was daraus so wird.
Und selbst wenn er es so überhaupt nicht packt, hätte ich so den Ansatz
um optimalen Compile-Zeit-Code zu generieren und die Runtime-Typen
würden ihr Werk auch optimal durchführen (wenn man davon ausgehen darf,
dass die Runtime-Typen nur dort eingesetzt werden, wo es auch nötig
ist). Dann muss der Kodierer nur noch zwischen den beiden Fällen
unterscheiden können.
Mal sehen, das wird wohl noch ein Langzeitprojekt ;)
Das Problem ist auch noch, wenn ein Register mehrere verschiedene
Parameter enthält, musst du die zwangsweise alle auf einmal setzen, was
in laaangen hässlichen Parameterketten resultiert. Setzt man die
nacheinander in einzelnen Funktionsaufrufen, erhält man dank "volatile"
zig unnötige read-modify-write-zyklen. Lässt man das volatile weg,
funktionieren Dinge wie Warteschleifen (warten auf Flags) nicht mehr...
Hallo mal wieder!
Also das mit dem Volatile und den mehreren Aufrufen kann man ja lösen,
nur mit der Constant-Propagation ist es haarig.
Ich habe jetzt mal etwas rumgespielt und auch wenn ich fast keine Ahnung
von dem Assembler habe, der da entsteht, so habe ich folgende Schlüsse
gezogen:
1. Mit funktionslokalen Objekten und Speichern ist er echt ziemlich gut
bei der Optimierung.
2. Sobald ein Objekt global ist, scheint selbst eine
"whole-program-optimiziation" nicht zu erkennen, dass sich das Objekt
während der gesamten Programmlaufzeit nicht ändert. Damit fallen dann
leider die Optimierungen flach, die in der Funktion verwendet werden.
Gibt es bei 2. einen technischen Grund oder eine Abhilfe?
Hrmmm, in den Optimierungseinstellungen sehe ich, dass man noch den gcc
als Treiber für den Linker einstellen muss und dass es auch
Möglichkeiten gibt, der Maschinerie zu sagen, dass ein Objekt außerhalb
der Projekt-Übersetzungseinheit nicht sichtbar ist... Vielleicht sind
das die Informationen, die er noch braucht. Erstmal herausfinden, wie
man das alles einstellt.