Forum: Mikrocontroller und Digitale Elektronik STM32F4: Umstieg auf Eclipse + GNU Arm Embedded Plugin


von BastiDerBastler (Gast)


Lesenswert?

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!

von Dr. Sommer (Gast)


Lesenswert?

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_optimieren

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

von BastiDerBastler (Gast)


Lesenswert?

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.
1
  using namespace stm;
2
  namespace fsmc = stm::fsmc;
3
  namespace rcc  = stm::rcc;
4
5
  // So sehen Register-Deklarationen aus:
6
  // Parameter: Adresse, Typ, Reset-Wert, Read-Maske, Write-Maske (= Read-Maske per default)
7
  using ahb1enr = 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
8
  using ahb2enr = system_register< 0x40023800 + 0x34, uint32, 0x0, bits::mask_from_bits<uint32,7,6,5,4,0>::value >;
9
  using ahb3enr = system_register< 0x40023800 + 0x38, uint32, 0x0, 1 >;  // overlaps with apb1enr?
10
11
  // ahb1enr::template set< field1, field2, ... >();
12
  // ahb1enr::template reset< field1, field2, ...>();
13
  // ahb1enr::template assign< field_value1, field_value2, ... >();
14
15
  // So Feld-Deklarationen
16
  // Parameter: Register oder Register-Liste, Bits, Position
17
  using dma1 = make_field< ahb1enr, 1, 21 >;  // dma1-enable feld in ahb1enr
18
19
  // So Feld-Deklarationen, die in mehreren Registern auftreten.
20
  using mtyp = make_field< register_list< fsmc::bcr<1>, fsmc::bcr<2>, fsmc::bcr<3>, fsmc::bcr<4> >, 2, 2 >;
21
22
  // So Feld-Werte
23
  using dma1_enabled = make_field_value< dma1, 1 >;
24
25
  // Daraus werden zwei (read&write)s (vielleicht könnte man dort mit bitbanding noch etwas machen...)!
26
  rcc::enable< rcc::fsmc, rcc::dma1, rcc::dma2 >();
27
28
  //
29
  // Helfer-Funktionen (viel Arbeit, da alles abzudecken...) ...
30
  //   (die templates die dort nötig sind, nerven etwas, das muss noch irgendwie verbessert werden)
31
  //
32
33
  // Setze PSRAM mit Mux auf FSMC-Kanal 1, Bank 2
34
  fsmc::channel1<2>::template setup_control<fsmc::psram, fsmc::mux_enabled>();
35
36
  // "Bare Metal" (channel 1 bank2 control register)
37
  fsmc::bcr<2>::template assign_fields<fsmc::control_fields::mtyp<1>, fsmc::control_fields::muxen<1>>();
38
39
  //
40
  // Fehlererkennung
41
  //
42
43
  using violation_field = stm::make_field< fsmc::bcr<2>, 1 , 30 >;
44
  // error: static assertion failed: Field mask covers reserved bits.
45
46
  using violation_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>::template assign_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>::template assign_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.

von BastiDerBastler (Gast)


Lesenswert?

Okay, wie schaut das aus?
1
gpio::port_a::configure_range<
2
  7, 15,
3
  gpio::afr_fields::alternate1, gpio::moder_fields::output, gpio::otyper_fields::open_drain, gpio::ospeedr_fields::high_speed
4
  >();

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

von Dr. Sommer (Gast)


Lesenswert?

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.

von BastiDerBastler (Gast)


Lesenswert?

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.

von BastiDerBastler (Gast)


Lesenswert?

Du meintest bestimmt diese Bibliothek? Hatte nur nach C++ gesucht, nicht 
direkt nach Template-Bibliothek -.- Mal anschauen!

http://embeddedprogrammer.blogspot.de/2012/07/open-source-template-peripheral-library.html

von Dr. Sommer (Gast)


Lesenswert?

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.

von BastiDerBastler (Gast)


Lesenswert?

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.

von BastiDerBastler (Gast)


Lesenswert?

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

von Dr. Sommer (Gast)


Lesenswert?

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

von BastiDerBastler (Gast)


Lesenswert?

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 ;)

von Dr. Sommer (Gast)


Lesenswert?

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

von BastiDerBastler (Gast)


Lesenswert?

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?

von BastiDerBastler (Gast)


Lesenswert?

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.

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.