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