Eine minimale Hello-World Anwendung belegt auf STM32F3 ca. 20 kB Flash, wenn sie mit der Cube-HAL oder Arduino (was ja darauf basiert) erstellt wird.
Eine alternative Implementierung, die ich hier im Forum gefunden habe, braucht jedoch nur 5 kB. Der Effekt betrifft nicht nur den STM32F3 auf. Ich konnte ihn ebenso mit anderen STM32 Familien reproduzieren.
Mir ist durchaus Klar, dass das Framework alleine aufgrund seiner Struktur einen gewissen Overhead mit sich bringt. Aber dass es so viel ist, überrascht mich dann doch. Enthält die USB/CDC Implementierung der HAL vielleicht irgend welche nützlichen Zwangs-Features, die über die minimale CDC Implementierung hinaus gehen?
Falls es jemand nachvollziehen will, das ist mein vollständiger Arduino Sketch, der die 20 kB ergibt:
1
voidsetup(){
2
pinMode(PC13,OUTPUT);
3
}
4
5
voidloop(){
6
digitalWrite(PC13,LOW);
7
Serial.println("Tick");
8
delay(500);
9
10
digitalWrite(PC13,HIGH);
11
Serial.println("Tack");
12
delay(500);
13
}
Das andere Projekt (für die STM32 Cube IDE) habe ich angehängt.
Wer das angehängte Projekt compilieren will, muss die Suchpfade zur CMSIS Bibliothek einstellen. In meinem Fall:
Aber bitte hängt euch nicht an kleinen Unterschieden in den Compiler Optionen und loop() bzw. main() auf. Mir geht es nicht um 1 kB mehr oder weniger, sondern um den krassen Unterschied von 20 kB versus 5 kB.
Du hast doch den Sourcecode oder? Warum liesst du den dann nicht und beantwortest deine Frage selber? Ich meine wofuer ist der wohl sonst da? Du kannst auch deinen Compiler bitten ein Mapfile anzulegen wo du fuer jedes Byte nachschauen kannst wo es bleibt.
Die allgemeine Antwort ist, die Welt besteht heute aus unfaehigen und faulen Programmierern die nur noch ihren Source von sonst wo zusammenkopieren, die fuer alles fertige Libs brauchen. Keiner versteht mehr im Detail was sein Programm eigentlich macht. Also werden die immer fetter.
Achte darauf das du nicht selbst zu so einem Loser wirst. .-)
Warum liesst du den (source code) dann nicht und
beantwortest deine Frage selber?
Hast du dir mal den Code der HAL angeschaut? Wenn ja, dann kennst du die Antwort. Ich frage hier lieber, als mich so tief einzuarbeiten, denn für mich sind Mikrocontroller nur ein kleiner Bestandteil von Hobby Projekten - nicht meine Lebensaufgabe.
die Welt besteht heute aus unfaehigen und
faulen Programmierern
Beruflich bin ich Softwareentwickler, aber nicht für Mikrocontroller. Ja, ich benutze sehr viele Bibliotheken (oft hunderte in einem Projekt) ohne sie im Detail zu kennen. Niemand gibt mir die Zeit, mich so intensiv damit zu beschäftigen, weil das wirtschaftlich nicht funktioniert. Ich werde nicht dafür bezahlt, jedes Bit dreimal von allen Seiten zu bewundern.
Ein Bauarbeiter analysiert auch nicht alle Bauteile, aus denen er ein Haus zusammen setzt. Das Haus muss zügig fertig werden, nur das zählt.
Achte darauf das du nicht selbst zu so einem Loser wirst.
Beim Hobby hat man den Luxus, sich intensiver beschäftigen zu können, wenn man will. Mit dem angehängten kleineren Projekt habe ich das getan. Dort ist mir der Sinn von fast jeder einzelnen Zeile Code klar.
Die HAL ist mir allerdings eine Nummer zu groß. Dafür reicht meine Lebenszeit nicht aus. Und STM32 sind auch nicht die einzigen Mikrocontroller, mit denen ich hantiere. Wenn also jemand anderes die HAL gut kennt und meine Frage beantworten kann, dann wäre das super. Wenn nicht, dreht sich die Welt auch ohne Antwort weiter.
Wenn mir der Speicherplatz aus geht: Größere Speicher sind billiger als Arbeitszeit.
Anstatt mit dem (in der Tat umfangreichen und unübersichtlichen, und nicht unbedingt als Lektüre für den Anwender gedachten) Quelltext des HAL anzufangen, würde ich mir erst einmal die beiden erhaltenen Binärdateien (idealerweise im ELF Format) mit objdump vornehmen und schauen, welche Funktionen dort überhaupt eingebunden wurden. Die Namen der Funktionen findet man ja schon im entsprechender Header. Um zu sehen, wann und von wo sie aufgerufen werden, würde ich das ganze auch noch disassemblieren. Im Anschluss daran würde ich konkret diese Funktionen, die tatsächlich eingebunden und aufgerufen werden, im Quelltext des HAL suchen und einen Blick darauf werfen, was sie eigentlich tun. Das ist vielleicht zielführender, als in medias res zu gehen, wenn man noch gar nicht weiß, wo man die "Übeltäter" suchen soll.
Meine Vermutung: Die Arduino-Umgebung neigt dazu, eine Menge Initialisierung vor dem Aufruf von setup() vorzunehmen. Da werden der Timer für millis() gestartet, Standardwerte für Taktgeneratoren gesetzt, Interrupts eingestellt... Aus der Cube HAL werden daher einige Funktionen aufgerufen und somit eingebunden, die man eigentlich nicht bräuchte, um nur Hello World auszugeben. Und diese Funktionen sind nicht unbedingt klein, weil sie recht allgemein gehalten sind. Also wird eine Menge Code eingebunden, selbst wenn nur bestimmte Pfade innerhalb jeder Funktion tatsächlich ausgeführt werden, weil die Arduino-Initialisierung eben genau diese Funktion mit bestimmten Parameterwerten aufruft.
Wie gesagt, das ist nur meine Vermutung. Vielleicht hilft auch schon ein Blick auf den Arduino-Initialisierungscode, um von dort aus die aufgerufenen Funktionen zu identifizieren und dann gezielt unter die Lupe zu nehmen.
Um zu sehen, wann und von wo sie aufgerufen werden, würde ich das ganze
auch noch disassembliere
Ganz simpler Ansatz: die ELF Datei disassemblisieren, und durchschscrollen. Da sieht man recht schnell, welche Funktionen, oder Gruppen von Funktionen (typischerweise am Namen erkennbar, wie eben USBD_xxx) groß sind. "nm" kann auch die Größen der Funktionen ausgeben.
im Quelltext des HAL suchen und einen Blick darauf werfen, was sie
eigentlich tun.
Wenn du das schon mal gemacht hättest, würdest du es nicht empfehlen. Der USB Code ist extrem komplex verschachtelt. Daraus erschließt sich mir nicht, was den 4x so großen Code rechtfertigt.
Nochmal, die Frage war: Was kann die HAL Implementierung, was die andere
kleinere Implementierung aus diesem Forum nicht kann?
Vermutlich alle Versionen "können" die mit einem STM32 an USB-
Implementierungen möglich sind.
Schaue ich mir das in CubeMX an, dann gibt es da eine ganze Menge:
Communication Device Class
Audio Device Class
Download Firware Update Class
Human Interface Device Class
Custom Human Interface Device Class
Mass Storage Class
Da wird nicht alles "von Haus aus" enthalten sein, aber der Kern-
Code muss so strukturiert sein dass alle Implementierungen
möglich sind. Das wird sicherlich einen Code Overhead verursachen.
der Kern-Code muss so strukturiert sein dass alle Implementierungen
möglich sind. Das wird sicherlich einen Code Overhead verursachen.
Sicher, aber kann das schon der Grund für ein 4x so großes Binary sein? Ich mag es nicht glauben. Sorry, aber ich bin von 8 Bit Mikrocontrollern anderes gewohnt (auch mit USB).
Kann die HAL ein seriöses Framework sein, wenn sie so schluderig mit der Code-Größe (und damit auch Performance) umgeht? Noch hoffe ich auf eine bessere Erklärung, die nicht so abtörnt.
Wenn du das schon mal gemacht hättest, würdest du es nicht empfehlen.
Der USB Code ist extrem komplex verschachtelt. Daraus erschließt sich
mir nicht, was den 4x so großen Code rechtfertigt.
Tatsächlich habe ich das schon mal gemacht (für den F4 statt für den F3, aber die Komplexität dürfte ähnlich sein). Ja, es stimmt, der Code ist verschachtelt - aber mit nm oder objdump bekommt man ja immerhin eine Liste der Funktionen und kann sich erst einmal die einfachen vornehmen, statt der Verschachtelung zu folgen.
Hatte ich vergessen zu schreiben, daß er auch ohne Arduino auf die ca.
20 kB kommt? Arduinos Overhead ist hier ausnahmsweise mal irrelevant.
Stimmt, du hast geschrieben, dass es auch nur mit Cube-HAL 20kB sind. Mir war nur entgangen, wie du den kleineren Code bekommst. (Deinen angehängten Code hatte ich nicht kompiliert, da ich keine IDE benutze.)
Nach kurzer Durchsicht (falls ich morgen etwas mehr Zeit habe, nehme ich mir die Dateien mal im Detail vor - ich bin neugierig) würde ich nach wie vor auf ausführlichere Initialisierung bei Cube-HAL vs. deinem kleineren Code tippen. Außerdem sehe ich im HAL Code DMA (scheint dein Code nicht zu benutzen), die einige generische Funktionen mit sich bringt. Außerdem eine Menge Timer-Funktionen (die vermutlich nur einmal aufgerufen werden - Initialisierung...). Und der PCD (Peripheral Controller Driver) fällt ebenfalls auf. Wie gesagt, mit etwas mehr Zeit, schaue ich gerne etwas genauer rein.
mit etwas mehr Zeit, schaue ich gerne etwas genauer rein.
Das wäre echt super. Denn vielleicht verbirgt sich in dem ganzen Code irgendein nützliches Neben-Feature, womit man die Codegröße rechtfertigen kann (anstatt ihn "aufgeblasen" zu nennen).
Eigentlich will ich ja gar nicht über die HAL ab lästern. So programmiert man heute halt, das habe ich akzeptiert. Ich will nur verstehen, was zu der enormen Größe führt.
Nach dem MAP Fils ist alles mit drin
I2C, ADC, SPI ...
Kann es sein, dass du dich in die "Discarded input sections" verlaufen hast? Ich sehe im Output von nm und objdump nur ungenutzte IRQ Vektoren für die von dir genannte Peripherie.
Es ist also wohl nicht nur der USB-Code, sondern einfach alles in dem Framework ist erstaunlich groß.
Beim PC habe ich mich längst daran gewöhnt, aber beim Mikrocontroller jucken mir bei dem Gedanken die Fußnägel. Die Vernunft sagt mir "scheiß drauf", aber ich kann es nicht ignorieren. Gibt es dafür kein funktionierendes De-Sensibilisierungs Programm ? :-)
Es ist also wohl nicht nur der USB-Code, sondern einfach alles in dem
Framework ist erstaunlich groß.
Ja so ist es, die ganze CubeHAL ist ziemlich "nicht-smart" implementiert. Alles wird sehr umständlich und explizit zur Laufzeit berechnet, jeder Datentyp ist ein 32bit-Integer usw. Die einzelnen Architekturschichten und Module sind penibel voneinander getrennt, obwohl sie in der Praxis nie unabhängig genutzt werden können. Gerade auch weil C wenig Möglichkeiten zur Modularisierung bietet wird es dadurch ziemlich kompliziert und dann doch nicht mehr so toll wiederverwendbar.
Man könnte den Code dennoch als einigermaßen "sauber" oder "gut strukturiert" bezeichnen, aber effizient wird er dadurch nicht. Er ist eigentlich schon einigermaßen übersichtlich, wenn man sich mal rein gearbeitet hat. ST-intern gibt es sicherlich Übersichtsdiagramme die vieles klarer machen, die bekommen wir aber nicht zu sehen...
Ich glaube das ist Absicht, damit das Heer an Junior-Entwicklern einigermaßen damit klar kommt, was bei cleveren effizienten Strukturen nicht mehr der Fall wäre. Das Heer an Entwicklern braucht man, weil es sehr viele Zeilen Code sind, weil der Code so stringend strukturiert ist, weil das für die Junior-Entwickler nötig ist, weil man so viele Entwicklerstellen eben primär mit Juniors besetzen muss, derer man ein Heer braucht weil ...
Richtig lustig wird es dann wenn man noch ein RTOS à la Zephyr drüber stülpt, dann hat es nochmal mehrere Abstraktionsschichten über der STM32CubeHAL.
Das alles ist natürlich nicht nur bei ST so, sondern Industrie-Standard. Ach, und LLMs kommen mit dem sehr expliziten Code sicher auch besser klar.
Die allgemeine Antwort ist, die Welt besteht heute aus unfaehigen und
faulen Programmierern die nur noch ihren Source von sonst wo
zusammenkopieren, die fuer alles fertige Libs brauchen. Keiner versteht
mehr im Detail was sein Programm eigentlich macht. Also werden die immer
fetter.
Also ich habe ehrlich gesagt kein Interesse mich groß in USB und fremde Bibliotheken einzuarbeiten. Ich habe auch keine Lust selber Monate und Jahre zu verbrauchen, um große Frameworks zu entwickeln, die vollends durchgeprüft werden müssen.
Wozu soll ich das Rad neu erfinden? Und die paar kB jucken mich nicht, da ich die heutigen Mikrocontroller in der Regel eh nur zu einem Bruchteil voll bekomme.
Es ist also wohl nicht nur der USB-Code, sondern einfach alles in dem
Framework ist erstaunlich groß.
Dafür gibt es zwei Hauptursachen:
Die ganze Initialisierung passiert über Strukturen (und die sind dann auch noch standardmäßig zur Laufzeit beschreibbar), was dazu führt, dass sie sowohl im Flash als auch zur Laufzeit im RAM Platz verbrauchen.
Dieser Ansatz ist ganz schick, wenn irgendwas zur Laufzeit umkonfiguriert werden muss. Bloß unterstützt CubeMX das erstens nicht wirklich und zweitens braucht man es bei kleinen µC auch eher selten. Was natürlich der tiefere Grund dafür ist, dass CubeMX praktisch keine Unterstützung dafür liefert.
Das Interrupthandling ist massiv suboptimal. Es wird immer das gesamte Gedöhns mitgeschleppt, welches praktisch alle möglicherweise auftretende Situationen abdeckt, auch wenn oft in der konkreten Anwendung der Hardware 2/3 bis 3/4 davon unmöglich auftreten können. Und natürlich wird auch hier wieder massiv mit Strukturen im RAM gearbeitet, in denen die aufgelaufene Weisheit der Interuptroutine (aus der Abfrage der ganzen Hardware-Flags) etwas handlicher abgelegt wird, bevor der Callback des Frameworks aufgerufen wird.
Der unbestreitbare Vorteil ist hier, dass aus Sicht der Awnendung das Handling massiv vereinfacht wird. Es spielt fast keine Rolle mehr, ob Polling, Interruptbetrieb oder Interruptbetrieb mit DMA verwendet wird.
Beim PC habe ich mich längst daran gewöhnt, aber beim Mikrocontroller
jucken mir bei dem Gedanken die Fußnägel. Gibt es dafür kein
funktionierendes De-Sensibilisierungs Programm ? :-)
Man kann STM32 auch zu Fuß programmieren. Das macht aber sehr viel mehr Arbeit und ist allenfalls für die ganz kleinen STM32 zu empfehlen.
Der Witz ist allerdings: in vielen Fällen kann HAL-freier Code dafür sorgen, dass man mit einem deutlich kleineren STM32 auskommt, als man ihn bei Verwendung der HAL brauchen würde. Allerdings sind die Preisunterscheide der Hardware so gering, dass der erhöhte Aufwand bei der Software sich erst bei recht großen Stückzahlen auszahlt.
Achso, du wunderst dich, dass Programme größer werden, wenn du zusätzliche libraries verwendest?!
Was genau hat analogRead mit USB zu tun? Was Serial.println? Verwendet das println auch noch floats? Wunderst du dich dann auch, dass die ganze Fließkommaarithmetik mit dazu gelinkt wird? C-Compiler analysieren die formatierungsstrings nicht (Rust und OCaml tun das) und haben somit absolut Null Chancen den float-Krempel wegzulassen.
Achso, du wunderst dich, dass Programme größer werden, wenn du
zusätzliche libraries verwendest?!
Nein, ich staune über die Größe. Die USB Implementierung aus diesem Forum ist keine 5 kB groß, die HAL legt jedoch 10 kB drauf.
DigitalWrite und Serial.Println habe ich einzeln aufgelistet, damit man den Platzbedarf der Framework-Komponenten sehen kann. Damit keiner sagt: Dein USB Test ist nur so groß, weil er DigitalWrite und Serial beinhaltet.
Was genau hat analogRead mit USB zu tun?
AnalogRead habe ich zum Schluss aus Neugier hinzugefügt - auch weil ich ein Gefühl dafür habe, wie viele Zeilen Code direkte Registerzugriffe für diesen Schritt brauchen würden. Nämlich ganz sicher keine 5 kB, nicht mal 1/10 davon.
Das ist wohl eher das println. Scheinbar bist du dir nicht darüber bewusst was dahinter steckt. Ein putch wäre der einfachste Einstiegspunkt. println ist schon garnicht Teil der C-Standardbibliothek. Das öffnet schon Tür und Tor für irgendwelche Spinnereien.
Um einen analogen Eingang zu lesen, brauche ich mit klassischen
Registerzugriffen ganz sicher keine 5 kB
Ganz sicher nicht. Das analogRead bietet aber wohl (ich vermute) floats an. Und sonst noch irgend ein Gedöns das du in den header-Files rausfindest. Genau darauf zielte meine Frage nach den linker-options ab. Ordentliche Compiler/Linker werfen das nicht benötigte Zeug einfach wieder raus. Tw. kann man explizit floats ausschließen. Aber wenn man (der Hersteller) Spaghetti-code produziert und den dann noch 2 Tage eintrocknen lässst, dann kommt auch nur ein riesen Kneuel als ein Teil aus dem Topf raus. Das hat übrigens absolut nichts mit HAL zu tun. HAL bedeutet Hardware Abstraction Layer. Das ist eine dünne Schicht die Zugriffe auf die Hardware vereinfacht und vereinheitlicht. Der Compiler kann daraus fast immer direkte Registerzugriffe machen.
Wenn das bei dem Cube nicht geht, wäre das ein Argument für mich die Plattformwahl nochmal zu überdenken.
Nur durch Hinzufügen von einem simplen analogen Lesezugriff ist der Code erstaunliche 5 kB größer geworden. Für "eine dünne Schicht" ist das sehr viel Code.
Das analogRead bietet aber wohl (ich vermute) floats an.
Nun ist klar dass du überhaupt keine Ahnung von Arduino hast.
Genau darauf zielte meine Frage nach den linker-options ab.
Ordentliche Compiler/Linker werfen das nicht benötigte Zeug einfach
wieder raus.
Ja sicher, tut Arduino natürlich auch.
(HAL) ist eine dünne Schicht die Zugriffe auf
die Hardware vereinfacht und vereinheitlicht.
Der Compiler kann daraus fast immer direkte Registerzugriffe machen.
Du kennst weder Arduino noch die HAL von ST, hast nicht mal in ihren Quellcode geschaut. Also erspare uns bitte solche dumm falschen Aussagen.
Tu mir einen Gefallen: Halte dich aus dieser Diskussion heraus.
Du kennst die HAL von ST nicht, hast nicht mal in ihren Quellcode
geschaut.
Nein, kenn ich nicht mehr (mehr). Vor über 10 Jahren hab ich mal einem Freund was damit aus Gefallen programmiert. Das hat mich überzeugt, dass der code von Armateuren geschrieben wurde. Aber der Begriff HAL ist mir durchaus geläufig. Zu der Zeit als der Begriff geprägt wurde gabs so ein Arduino-Betreutes-Denken noch nicht mal.
Übrigens ist Arduino ein Framework und hat rein garnichts mit ST zu tun. Aber selbst das zu erkennen bist du nicht fähig.
Ich frage mich gerade, ob du mit dem Arduino Framework vertraut bist.
Ich weiß, dass du nicht den blassesten Schimmer von C hast. Ich weiß, dass dir der Begriff HAL nicht geläufig ist, denn jetzt verwendest du ihn synonym mit Framework. Gleichzeitig jammerst du aber, dass der code kompakter wird, wenn du auf die Arduino-Blase verzichtest. Da gehört schon einiges an Realitätsverweigerung dazu.
Du kannst natürlich gerne weiter in deiner Galaxie weiterarbeiten, solltest dich aber nicht darüber beklagen.
Nachdem ich 30 Jahre lang auf Registerebene Programmiert habe, darf ich mir ja wohl mal die längst etablierten Frameworks anschauen, und über Punkte diskutieren, die mir dabei besonders auffallen. Daran ist nichts verwerfliches.
Und was kann ich dafür, daß ST sein Produkt "Cube HAL" nennt?
Eine vernünftige Diskussion ist hier mit dir unmöglich. Also nochmal: Halte dich bitte aus dieser Diskussion heraus.
Da versteht aber jeder was anderes drunter. Wenn ich einen Elefanten "Maus" nenne, ist es immer noch kein Nagetier, trotz langer Zähne.
Ein wichtiger Punkt bei einer HAL ist die Abstraktion, d.h. dass das API "nach oben" Hardware unabhängig ist. Genau diese Funktion erfüllen die HALs vom Linux-Kernel, Windows, Android. Aber nicht die STM32CubeHAL - da ist das API je nach Hardware unterschiedlich. Teilweise sogar bei identischer Hardware: Das was beim STM32F4 das "SDIO" ist, heißt beim STM32F7 "SDMMC". Die Hardware ist sehr ähnlich, die Register größtenteils kompatibel. Aber die APIs der HAL heißen einmal SDIO und einmal SDMMC. Man muss also im gesamten Code die HAL-Aufrufe ändern, um vom F4 auf den F7 umzusteigen, obwohl die Hardware nahezu gleich ist. Ist zwar hauptsächlich suchen & ersetzen, aber trotzdem blöde dass man nicht die selbe Codebase für beides nutzen kann.
deswegen habe ich es bereits im Eröffnungsbeitrag so genannt.
und hat rein garnichts mit ST zu tun.
Es geht hier um den STM32duino Core. Das ist die konkrete Implementierung von Arduino, auf Basis der Cube HAL. Beides kommt von ST. Das map File meines Arduino Sketches und die Ausgabe von nm haben bestätigt, dass überwiegend die HAL für den großen Code verantwortlich ist.
Außerdem schrieb ich bereits im Eröffnungsbeitrag, dass auch eine reine HAL Anwendung ebenso groß ist.
Aber die APIs der HAL heißen einmal SDIO und
einmal SDMMC.
Und das macht jetzt genau was besser? Dass ST nicht mal den Begriff HAL richtig verstanden hat? Sollte das nicht Plattformunabhängig sein? Also sowohl Arduino und HAL? Zumindest beim Begriff HAL sind wir uns ja wohl einig.
Ja, genau du wirst mir das verbieten. Ich warte schon drauf! Versuch einfach meine Argumente zu verstehen und entspann dich. Versuch einfach zu verstehen was println letztendlich bedeutet und friss die Kröte die du dir selbst bestellt hast. Versuch rauszufinden was hinter analogRead steht und verwende es nicht mehr wenn es zu viel code generiert.
trotzdem blöde dass man nicht die selbe Codebase für beides nutzen kann.
Ja, finde ich auch schade. Wenn ich wie du imstande wäre, die USB Schnittstelle selbst zu programmieren, würde ich die HAL (und damit auch STM32duino) komplett ablehnen.
Um so verwunderlicher ist es aber, dass selbst 30 Jahre
nicht geholfen haben zu verstehen woher der aufgeblasene code kommt.
Erkläre du es mir! Was habe ich übersehen?
Aber schaue dir dieses mal die Dateien an und probiere es aus. Auf noch mehr falschen Blödsinn habe ich nämlich keine Lust.
Die Frage im Eröffnungsbeitrag war:
Mir ist durchaus Klar, dass das Framework alleine aufgrund seiner
Struktur einen gewissen Overhead mit sich bringt. Aber dass es so
viel ist, überrascht mich dann doch.
Enthält die USB/CDC Implementierung der HAL vielleicht irgend
welche nützlichen Zwangs-Features, die über die minimale CDC
Implementierung hinaus gehen?
Abstraktionen wie eine HAL haben Vor- aber auch Nachteile. Wenn mal etwas nicht funktioniert was nicht davon abgedeckt ist und man muss auf Register Ebene herunter, ist es sehr mühsam herauszufinden warum die HAL ein bestimmtes Bit nicht gesetzt hat. Ich hatte gerade kürzlich das Vergnügen mit den Sync Eingängen vom DMAMUX und DMA bei einem H7.
Der schlägt schon mit 0x976 = 2422 bytes zu Buche. Die Funktion HAL_PCD_IRQHandler sieht zwar auf den ersten Blick kompakt aus, allerdings wird die Funktion PCD_EP_ISR_Handler inline eingebunden. So weit ich das auf den ersten Blick sehe, werden da mehr mögliche Fälle behandelt, als im HAL-freien Interrupt-Handler (und die im kleinen Code vermutlich gar nicht auftreten, weil sie zu Dingen gehören,die nicht benutzt werden).
Die "umfangreiche" Interrupt-Behandlung hatte 1984now ja auch schon erwähnt.
Dann sind mir noch folgende Funktionen zum Systemtakt aufgefallen:
1
08001608 g F .text 00000138 HAL_RCC_ClockConfig
2
08001740 g F .text 00000208 HAL_RCCEx_PeriphCLKConfig
Das sind zusammen 0x7dc = 2012 bytes. Wie ich schon vermutet habe, sind das generische Initialisierungsroutinen. Je nachdem, welche Parameter übergebe werden, initialisieren sie eine Taktquelle und konfigurieren diese, und daher ist natürlich Code für alle vorhandenen Quellen und Optionen vorhanden - auch wenn der Anwender davon in der Praxis nur eine benutzt. Das lässt sich durch direkten Registerzugriff, der die eine tatsächlich benötigte Konfiguration vornimmt, natürlich massiv verkleinern.
Immerhin 0x46e = 1134 bytes für USB_EPStartXfer - das lässt sich auch kompakter lösen, wenn man nur CDC haben möchte und keinen generischen Code...
So weit ich das bis hierhin sehe, sind im HAL keine Zusatzfunktionen, von denen der Anwendungscode irgendwie Gebrauch machen würde. Der größte Teil scheint einfach "toter" Code zu sein, der in der konkreten Anwendung nie ausgeführt wird, und einfach nur da ist, weil man den HAL ja auch für andere Dinge nutzen könnte, wenn man das denn wollte.
Wie schon von anderen hier erwähnt, liegt der Vorteil des HAL meiner Meinung nach nur darin, dass der Anwendungsprogrammierer hier ein generisches Interface bekommt und sich nicht um die individuellen Register kümmern muss, die sich je nach STM32-Familie doch merklich unterscheiden. Ich würde vermuten, dass sich der Code mit HAL leichter auf andere Familien portieren lässt als ohne HAL.
Zugegebenermaßen könnte man das sicher auf platzsparender erreichen. Anstatt z.B. die Taktkonfiguration durch eine Fallunterscheidung in einer generischen Funktion zur Laufzeit vorzunehmen, könnte man diese Fallunterscheidung auch schon beim kompilieren vornehmen, und dem Anwendungsprogrammierer ein dafür passendes Interface geben (z.B. separate Funktionen für HSE, HSI, LSE, LSI). Einen generischen Interrupt-Handler um ungenutzte Fälle zu verschlanken ist vermutlich etwas schwieriger umzusetzen.
Hab ich zwar schon mal, aber ich probier es nochmal anders:
Was ist das Equivalent von println in der C-Standard-Library?
Was hängt dann da noch mit dran?
Gäbe es eine noch einfachere Lösung in C die minimal Resourcen verbraucht?
o weit ich das bis hierhin sehe, sind im HAL keine Zusatzfunktionen,
von denen der Anwendungscode irgendwie Gebrauch machen würde. Der größte
Teil scheint einfach "toter" Code zu sein
Was ist das Equivalent von println in der C-Standard-Library?
Was hängt dann da noch mit dran?
Gäbe es eine noch einfachere Lösung in C die minimal Resourcen
verbraucht?
1
voidsetup(){}
2
voidloop(){}
ergibt 18 kB ohne lto und 15 kB mit lto
Wenn ich dann noch USB/CDC deaktiviere, sind es 4,2 kB ohne lto und 2,8 kB mit lto.
Das angehängte Programm kommt auf 20 kB ohne lto und 17 kB mit lto.
Es wurde von Cube MX generiert, mit Cube HAL, ohne Arduino, ohne printf.
Falls du nochmal mit dem 5 kB Code aus dem Forum vergleichen willst: den findest du im Anhang des Eröffnungsbeitrages.
Wenn ich dann noch USB/CDC deaktiviere, sind es 2,8 kB.
Also unabhängig davon, ob du USB verwendest wird USB-code mit generiert und gelinkt.
Das ist gelinde gesagt eine wenig professionelle Umgebung.*) Aber wenn ST es schon nicht hinbekommt eine durchgängige Benamsung bei der HAL hinzubekommen, dann vermute ich, dass ST nur mal schnell irgendwas mit Duino hinpfuschen wollte für den Hobbybereich. Für mich wäre das dann klar das Signal die Finger von STCube zu lassen.
Wenn man sich aber bewusst drauf einlässt, dann kann man sich auch nicht darüber beklagen.
*) Wenn die Nicht-Verwendung von USB nicht mal der Compiler/Linker schnallt, vermute ich immer noch falsche Einstellungen im Projekt. Aber das hab ich ja schon mal gesagt.
Also unabhängig davon, ob du USB verwendest wird USB-code mit generiert
und gelinkt.
Ja logisch, das muss so sein. Das Ding soll sich schließlich als CDC Device beim PC zu erkennen geben, weil ich CDC Support aktiviert habe.
vermute ich immer noch falsche Einstellungen im Projekt
Zeige mir, was ich einstellen soll. Gerne am Beispiel des leeren Sketches, der Einfachheit halber. Oder an dem gerade geposteten Programm für die Cube IDE, da hast du mehr Einstellmöglichkeiten.
vermute ich, dass ST nur mal schnell irgendwas mit
Duino hinpfuschen wollte für den Hobbybereich
Ich verstehe deinen Eindruck. Vor ein paar Jahren war ich auch mal so weit, aber inzwischen pflegen sie das STM32duino Projekt seit 8 Jahren und die HAL ist noch älter. Die meinen das wohl ernst.
Kennst du eine Alternative? Wie komme ich als Hobbybastler an eine andere/bessere USB Implementierung für die aktuellen Modelle (ich denke dabei an die STM32G Familie)?
Nein, mit STM hab ich keine Erfahrung. Bei den PICs ist die HAL durchgängig, den Prozessor zu wechseln (nur innerhalb einer Familie tatsächlich gemacht) erfordert keine Code-Änderung. Gleiches für den Framework.
Allerdings erfordert auf den PIC32 USB FreeRTOS. Und darauf will ich mich nur unter Zwang einlassen. Da mach ich lieber Ethernet oder nimm einen FTDI. Der FTDI wäre ja auch eine Option für dich, wenn du deine 4-lagige Platine machst.
Du hast mich beleidigt und wie einen Dummkopf da stehen lassen. Du hast mehrfach von falschen Projekteinstellungen geschrieben, konntest dabei aber nicht helfen. Du hast Arduino und Cube HAL scharf kritisiert, konntest aber keine Alternative nennen.
Zu deiner Erleichterung muss ich sagen, dass bisher niemand helfen konnte. Allerdings wurden meine Erkenntnisse bestätigt.
Aus meiner Sicht sieht das so aus, dass ich alle Möglichkeiten bereits vor diesem Thread ausgeschöpft hatte. So viel dazu, dass ich keinen blassen Schimmer habe.
Nichts, aber es zeigt dass die CubeHAL eben nicht deiner Vorstellung einer HAL entspricht, auch nicht dahingehend dass sie nicht "dünn" ist. Im Übrigen ist die HAL von Windows, Linux, Android alles andere als dünn; gerade bei Android ist sie eigentlich das Hauptproblem für die Fragmentierung zwischen den unterschiedlichen Herstellern und der daraus folgende Mangel an Updates für ältere Geräte.
Du hast Arduino und Cube HAL scharf kritisiert,
konntest aber keine Alternative nennen.
Hast Du danach gefragt? Ich könnte dir eine nennen, aber die würde dir nicht gefallen. Daher lass ich das lieber sein, ich will dich nicht zu einer anderen Sekte bekehren und ich bin kein Missionar. Du willst Arduino, dann nimm es, aber beklag dich nicht.
Aber die STM32G haben schon wieder andere USB Peripherie.
Also auf den ersten Blick sieht es ziemlich ähnlich aus. Die haben auch die EPnR-Register. Vermutlich muss man nur ein paar Details anpassen, z.B. das jetzt verfügbare Handling des Pull-Up Widerstands auf D+ (enorm praktisch).
Kennst du eine Alternative? Wie komme ich als Hobbybastler an eine
andere/bessere USB Implementierung für die aktuellen Modelle (ich denke
dabei an die STM32G Familie)?
Auf den ersten Blick sieht es schlanker aus als Cube HAL, wobei es vermutlich auch weniger Funktionalität bietet. STM32G0 und STM32G4 werden unterstützt. Immerhin gibt es ein USB/CDC Beispiel, hier für STM32F3:
Allerdings muss ich dazu sagen, dass ich weder Cube HAL, noch LibOpenCM3 bisher für eigene Projekte benutzt habe, und auch kein USB in meinen Projekten benutzt. Von daher kann ich nicht sicher sagen, ob mein erster Eindruck zutreffend ist.
Wie ich schon vermutet habe, sind
das generische Initialisierungsroutinen. Je nachdem, welche Parameter
übergebe werden, initialisieren sie eine Taktquelle und konfigurieren
diese, und daher ist natürlich Code für alle vorhandenen Quellen und
Optionen vorhanden - auch wenn der Anwender davon in der Praxis nur eine
benutzt.
Diesen Teil kann man allerdings (mal abgesehen vom Platzbedarf der Strukturen) durch Verwendung der Optimierung (insbesondere LTO) noch einigermaßen in den Griff bekommen.
Das klappt aber nicht für die ISRs! Hier ist die Intelligenz von Compilern einfach mal nicht ausreichend. Um hier besser zu werden, müßten sie verstehen, wie die Hardware funktioniert. Also z.B. wissen, dass sich dieses und jenes Flagbit bei der gegebenen Initialierung der Hardware mit absoluter Sicherheit nicht ändern wird, also alle Verzweigungen im ISR-Code, die nur existieren, um den Fall zu behandeln, dass es sich ändert, in der konkreten Anwendung einfach mal überflüsig sind.
Das können sie nicht und werden sie wahrscheinlich auch nie "lernen" (also beigebracht bekommen) können.
Oder anders ausgedrückt: Hier läßt sich durch manuelle Implementierungen am meisten sparen, sowohl Speicherplatz als insbesondere auch Laufzeit.
Aber immerhin: Die HAL enthält zum Glück die Möglichkeit, hier ohne große Klimmzüge reinzugrätschen. Man kann als weite Teile des Standard-Handling problemlos "abklemmen" (und effektiv wirklich aus dem Code entfernen). Aber ist dann natürlich selber dafür verantwortlich, einen bezüglich der Anwendung vollständigen und fehlerfreien Ersatz bereitzustellen.
Und das Allerbeste ist: man kann aus diesem eigenen Teil dann auch wieder den Standard-Callback der HAL aufrufen. Aus Anwendungssicht muss sich also dadurch nicht mal zwingend etwas ändern.
Um das alles allerdings tun zu können, muss man die HAL schon ziemlich gut verstanden haben, insbesondere deren Schicht-Struktur. Die ist leider nirgendwo wirklich gut erklärt. Man muss sich in den HAL-Code einlesen, um das Konzept zu verstehen. Bei einfachen Sachen wie etwa UART, I2C oder SPI ist das für einen erfahrenen Entwickler noch mit recht überschaubarem Aufwand möglich, aber bei USB wird's schon ziemlich heftig, da verliert man schnell mal die Orientierung...
Bei einfachen Sachen wie etwa
UART, I2C oder SPI ist das für einen erfahrenen Entwickler noch mit
recht überschaubarem Aufwand möglich, aber bei USB wird's schon ziemlich
heftig, da verliert man schnell mal die Orientierung...
Zu blöd, dass USB gerade der Teil ist, weswegen ich mir die HAL und Arduino anschaue. Alle anderen Schnittstellen kann ich "zu Fuß" programmieren.
Zu blöd, dass USB gerade der Teil ist, weswegen ich mir die HAL und
Arduino anschaue. Alle anderen Schnittstellen kann ich "zu Fuß"
programmieren.
OK. Dann als erstes mal ganz beherzt den Arduino-Kram weglassen. Das ist nun wirklich eine völlig unnötige Abstraktionsschicht on top, die nicht nur unnötigen Overhead einführt, sonder obendrein alles weitere bezüglich einer Optimierung nur unnötig verkompliziert.
Du bist jetzt schon der zweite, der behauptet, ich müsse nur die
Optimierungen richtig einstellen, ohne konkret zu werden.
Nein, das habe ich keinesfalls behauptet, sondern klar abgegrenzt, dass das nur bezüglich der Initialiserung etwas bringen kann, nicht aber bezüglich all dem Scheiß, der als Folge von Interrupts passiert, sondern dass da Handarbeit zur Optimierung nötig wird.
Du kannst also offensichtlich nicht mal deutschen Text lesen. Schlechte Voraussetzungen...
Vielleicht ist dir aber auch nur nicht klar, dass bei USB ein sehr großer Teil der Funktionalität in den Handlern von ISRs steckt (stecken muss).
Vielleicht ist dir aber auch nur nicht klar, dass bei USB ein sehr
großer Teil der Funktionalität in den Handlern von ISRs steckt (stecken
muss).
Das ist mir durchaus klar. Der kleinere Code, denn ich ganz oben im Eröffnungsbeitrag angehängt hatte, zu dem ich schrieb, dass ich ihn Zeile für Zeile analysiert und weitgehend verstanden habe, besteht zu 90% aus einer einzigen ISR.
Umso mehr erstaunt mich, dass du es für möglich hältst, den 20 KB Code mit HAL signifikant zu verkleinern. Schade, dass du diesbezüglich nicht konkreter werden möchtest.
Wirf den Cube in die Tonne und schluck das als Lehrgeld.
Evtl. kommst Du mit Keil MDK (https://developer.arm.com/Tools%20and%20Software/Keil%20MDK) weiter. K.A. was das kostet. Von Keil gibts glaub ich eine freie (und eingeschränkte) Version. Das musst du aber selber rausfinden.
Sicherlich gibts auch eine komplett freie Toolchain. Der gcc sollte ja wohl arm können.
Umso mehr erstaunt mich, dass du es für möglich hältst, den 20 KB Code
mit HAL signifikant zu verkleinern. Schade, dass du diesbezüglich nicht
konkreter werden möchtest.
Naja, wollen wir dich nicht mal ganz doof sterben lassen. Der Trick ist: Funktionalität aus ISRs entfernen.
Sprich: Das Konzept umsetzen, was auch richtige OS verwenden. Auch die sind nämlich mit der dramatischen Unfähigkeit der Compiler geschlagen. Die Lösung ist typisch: Es gibt noch eine zusätzliche Schicht zwischen Applikation und ISRs. Und zusätzlich: die eigentliche Applikation läuft nicht wirklich auf main()-Level, sondern nur dann, wenn das, was nunmehr auf main()-Level läuft, also diese zusätzliche Zwischenschicht, mal Zeit läßt für Anwendungscode.
Reichere das noch mit entsprechenden Gimmicks an, und du bist du bei einem typischen RTOS. Man muss aber nicht notwendigerweise so weit gehen. Ein "angedeutetes RTOS-Konzept" reicht halt in vielen Fällen auch.
Und falls du dich fragst, warum das nun bezüglich der Größe des Codes effizienter ist: weil der recht doofe Compiler so seine Fähigkeiten zur Optimierung wesentliche besser ausspielen kann. In main() beherrscht er das nämlich ziemlich gut...
Das Projekt scheint vor vielen Jahren gestorben zu sein. Das es die neuen STM32 Modelle nicht (bzw. nur unvollständig) unterstützt, ist es für mich uninteressant.
Also der GCC kann die CubeHAL kleiner kompilieren als der GCC der bei
Arduino oder STM32CubeIDE mitgeliefert ist?
Quatsch. Hör endlich auf dir so wirres Zeug zusammen zu fantasieren.
Ich habs schon mal gesagt:
Was das CubeHAL macht ist einfach schlecht angelegt. Die sollten sich mal den Unterschied von modularer und monolithischer Programmierung ansehen. Und dann ihr Zeug entsprechend zerfleddern und neu strukturieren.
Dass das aber bei dir jemals ankommt bezweifle ich inzwischen massiv.
Dann wirst du wohl mit der Codegröße leben müssen.
Wie beim Auto. Wenn ich einen Ferrari F350 fahren will, muss ich halt auch damit leben können, das 'ne blöde Inspektion ein paar Tausender kostet und die Reparatur einer auf dem Parklatz eingedellten Front mal eben mit 35000€ zu Buche schlägt.
Noch was zum Thema: Wenn man unbedingt will, kann man USB sicher auch ohne HAL verwenden. Dadurch das der USB-Core eingekaufte IP ist, ist die Philosophie, die dahinter steckt eine andere als bei der restlichen Peripherie von ST. Ich habe für meine Testimplementierung damals dann doch die HAL genommen und war relativ schnell fertig mit meiner Applikation.
Aber dem TO scheint es wichtiger sein zu lamentieren statt zu implementieren.
Für Privatanwender / Hobbyisten? Zu viel. (irgendwas im oberen kEuro-Bereich...)
Von Keil gibts glaub ich eine freie (und
eingeschränkte) Version.
Zum Glauben geht man in die Kirche.
Es gibt eine Version, die auf eine maximale Codegröße beschränkt ist.
Die unterstützt aber nicht alle Funktionalitäten der Vollversion.
Ein USB-Device kann man damit AFAIR programmieren - einen USB-Host nicht.
Ich arbeite dienstlich mit der Vollversion, daher weiß ich nicht genau, ob USB von der beschränkten Version überhaupt unterstützt wird.
Ist aber sinnfrei, wenn man Privatanwender ist, und die CubeIDE verwendet.
Nur mal so als uninteressante Zusatzinfo: Bei der libopencm3 gibt's in der Nachbarschaft auch uninteressante libopencm3-examples. Dort u.a. unter:
libopencm3-examples/examples/stm32/f4/stm32f429i-discovery/usb_cdcacm
ein uninteressante Simpelbeispiel, was wohl nur so eine Art 1:1 RX-TX Verbindung per USB macht:
Die allgemeine Antwort ist, die Welt besteht heute aus unfaehigen und
faulen Programmierern die
... jeden Scheiß selbst von Hand machen, weil sie sich für die genialsten Entwickler unter G~ttes schöner Sonne und alle anderen für unfähige und faule Vollidioten halten.
Als Kollegen sind solche Leute eine absolute Pest. Jeder Quatsch wird extra manuell implementiert, jedes Mal neu und nach ganz eigenen "Standards", und das Ergebnis ist ein Kartenhaus aus Not-Invented-Here, das augenblicklich in sich zusammenfällt und exorbitante Aufwände erforderlich macht, sobald sich die Anforderungen nur minimal ändern. Zudem sind solche Leute extrem langsam und ziehen die Produktivität ihres ganzen Teams in den Keller, was sie dann zwar mit ihrer besonderen Sorgfalt zu rechtfertigen versuchen, aber...
Achte darauf das du nicht selbst zu so einem Loser wirst. .-)
Achte vor allem sehr genau darauf, Dich von solchen Losern fernzuhalten.
Jeder Quatsch wird extra manuell implementiert, jedes Mal neu und nach
ganz eigenen "Standards",
Was wenn man Dinge nach etablierten Standards neu implementiert, aber nach neumodischen Standards - z.B. OOP aus den 80ern statt strukturierter Programmierung aus den 60ern?
Noch was zum Thema: Wenn man unbedingt will, kann man USB sicher auch
ohne HAL verwenden.
Noch habe ich reichlich STM32F303 Board vorrätig, von denen ich zehre. Sollte ich irgendwann auf neue Modelle (z.B. STM32G4) wechseln müssen, steht mir ja immer noch die von Niklas empfohlene Möglichkeit offen, den Code aus dem Eröffnungsbeitrag zu portieren.
Aber dem TO scheint es wichtiger sein zu lamentieren statt
zu implementieren.
Ich wollte eigentlich nur die Frage aus dem Eröffnungsbeitrag klären:
Enthält die USB/CDC Implementierung der HAL vielleicht irgend
welche nützlichen Zwangs-Features, die über die minimale CDC
Implementierung hinaus gehen?
Lamentiert haben andere, die auf den Optimierungseinstellungen herum geritten habe, obwohl ich ausdrücklich darum bat, das nicht zu tun:
Was wenn man Dinge nach etablierten Standards neu implementiert, aber
nach neumodischen Standards - z.B. OOP aus den 80ern statt
strukturierter Programmierung aus den 60ern?
Darum ging es nicht, aber das weißt Du ja selbst. Wenn Du das diskutieren möchtest, dann mach dazu bitte einen eigenen Thread auf.
Noch habe ich reichlich STM32F303 Board vorrätig, von denen ich zehre.
Sollte ich irgendwann auf neue Modelle (z.B. STM32G4) wechseln müssen,
steht mir ja immer noch die von Niklas empfohlene Möglichkeit offen, den
Code aus dem Eröffnungsbeitrag zu portieren.
[...]
Du könntest dir mal http://www.tinyusb.org ansehen. Das ist mit Sicherheit nicht so kompakt wie das Beispiel aus dem Eröffnungsbeitrag, aber schmaler als der HAL-USB-Code.
Ich habe mir gerade zwei STM32G431CBT6 Boards für sagenhafte 9 Euro (incl. Versand) bestellt. Im nächsten Urlaub werde ich versuchen, den USB Code aus dem Eröffnungsbeitrag darauf zu portieren.
Sollte ich irgendwann auf neue Modelle (z.B. STM32G4) wechseln müssen,
steht mir ja immer noch die von Niklas empfohlene Möglichkeit offen, den
Code aus dem Eröffnungsbeitrag zu portieren.
Also mein USB-Treiber sollte sich sowohl auf den F3 als auch auf den G4 portieren lassen, die haben nahezu identische Hardware wie der F1, für den ich den Treiber ursprünglich geschrieben hatte.
Das ist doch W.S. Implementierung von USB/CDC. Gab's da von ihm mal eine Lizenzaussage? Wie groß ist denn der Unterschied zwischen der 103 Version von deiner Seite https://stefanfrings.de/stm32/stm32f1.html#vcpnohal und der 303 in diesem Thread?
Wenn es keine Lizenz gibt, ist der Code eben nicht frei - dann darf man ihn lesen, mehr nicht. Alles was nicht explizit erlaubt ist, ist verboten. Daher ist das Angeben von Lizenzen bei OpenSource-Projekten ja so wichtig, weil der Code sonst nicht verwendbar ist. Aber W.S. hat sich nach vielem Murren ja überreden lassen, eine Lizenz in seiner besonderen Ausdrucksweise anzugeben.
Die kommen beide von dieser Webseite und unterscheiden sich lediglich in Konfigurationsparametern in der usb.c, sowie uC spezifische Konfiguration der Taktquellen und I/O Pins in main c.
Wenn es keine Lizenz gibt, ist der Code eben nicht frei - dann darf
man ihn lesen, mehr nicht. Alles was nicht explizit erlaubt ist, ist
verboten. Daher ist das Angeben von Lizenzen bei OpenSource-Projekten ja
so wichtig, weil der Code sonst nicht verwendbar ist.
Absolut richtig. Das ist ja gerade der Trick der GPL: man darf den Code nur dann benutzen, wenn man sich an seine Lizenzbedingungen hält.
Es ist aus meiner Sicht extrem erschreckend, daß sich sogar viele erfahrene Entwickler nicht mit Lizenz- und Nutzungsrechten auskennen.
Ich konnte die oben genannte kompakte USB Implementierung von W.S. auf meinem STM32G431 Board ans Laufen bringen. Wie bereits vermutet wurde, hat der G4 im Prinzip die selbe Peripherie. Nur die Konfiguration der I/O Pins und des Taktes ist (wie üblich) im Detail etwas anders.