Embedded-C-Anfänger-Problem: Habe mein erstes lauffähiges Programm für
den STM32, ein PWM mit DMA, womit ich einen WS2812 Led-Stripe ansteuere.
Jetzt würde ich gerne ein wenig Refactoring machen und die Logik für die
Led-Ansteuerung in ein eigenes Modul (leds.c) auslagern.
Mein Problem ist nun, dass das Timerdefinition struct von der IDE in das
main-File "hineingeneriert" wurde und ich in meiner leds.c keinen
direkten Zugriff darauf habe. Da, wo ich meine Funktion aus main.c
heraus aufrufe, könnte ich das als pointer hineinreichen.
Aber irgendwie hab ich dann noch das Problem, dass ich eine
HAL_TIM_PWM_PulseFinishedCallback handler definiere, der den DMA stoppt
(HAL_TIM_PWM_Stop_DMA). Dieser muss den Timer kennen und den channel.
Ich bin mir auch gar nicht sicher, ob ich so einen Callback überhaupt
außerhalb meiner main.c definieren sollte. Kann ich mehr als eine
einzige Implementierung davon in meinem Programm haben? Was, wenn ich
den für einen anderen Timer auch verwenden will, der gar nichts mit
meiner Led-Funktion zu tun hat?
Oder sollte ich vielleicht einfach das hadrwarenahe Timerzeug in meiner
main.c lassen und nur die Funktionen auslagern, die Daten aufbereiten,
die letztlich an die Led-Controller geschickt werden? Wäre immerhin eine
Art von Separation-of-Concern.
Mir fällt es leider schwer, meine Frage(n) sauber auf den Punkt zu
bringen.
Also irgendwie: Wie geht man vor mit generiertem HAL code und Callbacks,
Interrupts etc. und Zustand, wenn ich Code modular halten will und
globalen State vermeiden möchte? Habt ihr ein Paar best-practice-Tipps?
Danke.
Nach meinen Erfahrungen gibt es 2 Arten von Programmieren.
Die einen kriegen es raus, nachdem ihnen 2 mal das System im Chaos
versunken ist. Die anderen bekommen niemals den Bogen raus.
So Richtlinien bringen wenig - das eigentliche Problem sind
Erweiterungen für unvorhersehbare Anforderungen.
Du weißt halt nicht, ob du später mal diesen Timer vür mehrere Aufgaben
einsetzen musst. Ganz egal, wie du es anlegst, später kommen
Anforderungen, die nicht zu deiner Struktur passen. Du musst ein Gefühl
dafür entwickeln, wann wegverfen und neu strukturieren einfacher wird
als dran frickeln.
Ein Tipp schrieb:> So Richtlinien bringen wenig - das eigentliche Problem sind> Erweiterungen für unvorhersehbare Anforderungen.>
Von Richtlinien würde ich auch gar nicht reden, eher von Design
Patterns. In objektorientierten oder in funktionalen Sprachen habe ich
die Erfahrung gemacht, dass bestimmte Pattern, richtig angewendet eben
genau die Resilienz für unverhersehbare Anforderungen erhöhen. Aber im
Embedded-Bereich und in C allgemein habe ich noch kein "Gefühl" dafür
entwickelt und tue mich schwer damit, den hardwarenahen Teil vom "Rest"
zu separieren. Und die Tatsache, dass meine IDE Code generiert, bringt
noch mal eine spezielle Schwierigkeit mit, auf die ich noch keine gute
Antwort habe.
Mach dir Gedanken wie du deine gesamte Architektur aufbauen willst.
Du hast auch die Möglichkeit die Generierung der Interruptfunktionen zu
unterdrücken (Haken löschen in der IDE).
Deinen eigenen Code solltest du eh in eigene Dateien schreiben und dann
in der Main oder in den Tasks aufrufen.
Ich habe mir komplettes ExpansionPack geschrieben, in dem ich die CAN
Kommunikation inkl ein paar Transportprotokolle realisiert habe. Dieses
ExpansionPack generiert auch meine Interruptroutinen.
Also möglich ist alles 😀
Alles in main ist aber eher das Gegenteil von Modularisierung.
In den erweiterten Projekteinstellungen im Cube gibt es noch Optionen um
Komponenten aufzuteilen, vielleicht hilft das.
J. S. schrieb:> Alles in main ist aber eher das Gegenteil von Modularisierung.
Aber, du kommst so an Alles heran.
mit
1
htimX->Instance.<REGNAME>
kann man dann auch wieder auf die Registerstruktur zugreifen.
J. S. schrieb:> In den erweiterten Projekteinstellungen im Cube gibt es noch Optionen um> Komponenten aufzuteilen, vielleicht hilft das.
Das mach ich allerdings auch so - immer.
J. S. schrieb:> Alles in main ist aber eher das Gegenteil von Modularisierung.
Das hat nichts mit dem modularisieren zu tun. Darin stehen die ganzen
Definitionen welche CubeMX erzeugt. Ohne kannst niemals auf einen Timer
zugreifen.
Die eigene Modularisierung macht man, indem man die Funktionen für
Callback usw. in eine Funktion packt und eben in der main.c im Callback
diese aufrufst. Das kannst auch eigentlich nicht anders machen, da sich
manche Interrupts die Funktion teilen, entsprechend muss es irgendwo
eine Stelle geben die über alle Callbacks Bescheid weiß, und das ist in
der main.c.
In der eigenen Datei definiere ich einfach immer etwas wie
1
#define mytim &htim6
und nutze immer den. Das muss später natürlich entsprechend ausgefüllt
werden. Aber die main.h muss wegen der extern deklarierten Komponenten
eingebunden werden.
Hab was gefunden, was mich vielleicht weiterbringt. Im Projectmanager
der ioc-Perspektive gibt es eine Option um die Peripherieinitialisierung
in einzelne h/c files aufzuteilen.
J. S. schrieb:> Alles in main ist aber eher das Gegenteil von Modularisierung.> In den erweiterten Projekteinstellungen im Cube gibt es noch Optionen um> Komponenten aufzuteilen, vielleicht hilft das.
Genau. Auch gerade selbst drauf gestoßen. Hätte mal refreshen sollen
bevor ich es poste :)
Gerald M. schrieb:> Die eigene Modularisierung macht man, indem man die Funktionen für> Callback usw. in eine Funktion packt und eben in der main.c im Callback> diese aufrufst. Das kannst auch eigentlich nicht anders machen, da sich> manche Interrupts die Funktion teilen, entsprechend muss es irgendwo> eine Stelle geben die über alle Callbacks Bescheid weiß, und das ist in> der main.c.
Also die main.c stellt so eine Art Eventbus dar, der der Reihe nach alle
meine Module aufruft, die an einem bestimmten Hardware-Callback
interessiert sind? Macht irgendwie Sinn, auch wenn ich mich noch nicht
so ganz mit der Idee angefreundet habe.
um meinen Fall nochmal konkreter zu machen. Meine Funktion Display_send
schickt die Daten für die WS2812 auf PWM-Reise und blockiert bis sie ein
Flag über den Callback bekommt, wobei auch das PWM wieder gestoppt wird.
Wenn ich den callback in meiner main.c unterbringe und main.c dann
meinem Modul sagt, dass das Paket versendet wurde, reiße ich diesen
Zustandsautomaten irgendwie auseinander. Die Logik ist dann auf zwei
source files verteilt. Das behagt mir nicht so richtig.
Diese Functionen sind in der HAL als weak deklariert. Wenn du die
Generierung unterbindest, kannst du die "überall" hin schreiben und dann
wird die ursprüngliche überschrieben
Eine Komponente ist aber nur eine wenn sie frei ist von globalen
Ressourcen oder (angenommenen) Singletons. Beim init muss eben alles
mitgegeben werden damit diese unabhängig genutzt werden kann. Gilt auch
für Interrupts und DMA, aber gerade beim beim DMA ist es bei STM32 oder
auch Cortex—M mit den vielen Bussen schwierig.
Bei den ISR hilft die HAL ja schon mit der neuen Struktur in der die
callbacks einer Ressource zugeordnet werden und der weak Murks nicht
mehr nötig ist.
>> Diese Functionen sind in der HAL als weak deklariert. Wenn du die> Generierung unterbindest, kannst du die "überall" hin schreiben und dann> wird die ursprüngliche überschrieben
hab mich oben vielleicht missverständlich ausgedrückt bzw. mehrere
Probleme vermischt. Die Callback-Funktion wird nicht generiert, die
schreib ich selbst. Aber mein Verständnis war, dass ich
HAL_TIM_PWM_PulseFinishedCallback nur einmal in meinem ganzen Programm
nutzen kann. Aber ich kann mir durchaus vorstellen, dass ich noch
weitere Funktionen habe, die den Callback brauchen, die mit der
LED-Steuerung nichts zu tun haben (bei mir nicht der Fall gerade).
Na klar kann es die HAL_TIM_PWM_PulseFinishedCallback auch nur einmal
geben.
Und sie sollte eben in einem Modul stehen welche sich mit dem TIM
beschäftigt.
Ergo baut man sich ein "TIM-Steuer-Modul" welches dann auch Funktionen
hat wo d deine PWM mit einfachen Parametern einschalten kannst.
Dann hat das Module entweder eine Task-Funktion die aktiv zurück meldet
wenn etwas fertig ist, oder alternativ hat das Modul eine Funktion
welche du ohne zu blockieren abfragen kannst wann die Arbeit erledigt
ist.
Jens R. schrieb:> Na klar kann es die HAL_TIM_PWM_PulseFinishedCallback auch nur einmal> geben.>> Und sie sollte eben in einem Modul stehen welche sich mit dem TIM> beschäftigt.> Ergo baut man sich ein "TIM-Steuer-Modul" welches dann auch Funktionen> hat wo d deine PWM mit einfachen Parametern einschalten kannst.>> Dann hat das Module entweder eine Task-Funktion die aktiv zurück meldet> wenn etwas fertig ist, oder alternativ hat das Modul eine Funktion> welche du ohne zu blockieren abfragen kannst wann die Arbeit erledigt> ist.
jetzt formt sich langsam eine Idee, wie ich's mache. Danke für den
Schubser!
Du schreibst ja leider als Gast.
Wenn du mal anschreibst, kann ich dir mal mein Projekt als
Gedankenanstoß zu kommen lassen. Das werde ich zwar auch mal öffentlich
stellen, doch bisher ist es noch zu unordentlich :-D
Jens R. schrieb:> Dir ist aber schon klar, wenn man die Interruptroutinen selber einhängen> möchte, dass man dann die gesamte HAL raus schmeißen muss 🙄
vielleicht erstmal nen Kaffee trinken und dann genauer hingucken?
Man braucht den Murks mit den **weak** Funktionen nicht mehr. Und ISR
ist nicht Callback. Die ISR lässt man wie sie ist, und verwendet die
neueren Callbacks die mit einer Funktion registriert werden und nicht
mehr die veralteten weak Funktionen. In anderen Codeteilen der HAL sind
diese Funktionen schon als 'legacy' kommentiert.
Jens R. schrieb:> Und warum verlinkst du zwei mal die gleiche Datei?
um zu zeigen das es die cb für die verwendeten Funktionen gibt. Tja,
zweimal gezeigt und immer noch nicht verstanden worum es geht.
Und warum schreibt man dann nicht
Kein Zitat:
> verwende lieber die Funktionspointeraufrufe anstatt der weak Funktionen 🙄
Wobei es ja an der Grundaufgabe hier nichts ändert.
Der Fragensteller möchte die Callback Funktion (welche ja über die ISR
aufgerufen wird) an eine Stelle schreiben welche in seinem Kopf einen
Sinn ergibt. Und dabei ist es unerheblich wie diese aufgerufen (Pointer
vs weak) wird.
Jens R. schrieb:> Und warum schreibt man dann nicht
warum liest man das nicht?
J. S. schrieb:> Bei den ISR hilft die HAL ja schon mit der neuen Struktur in der die> callbacks einer Ressource zugeordnet werden und der weak Murks nicht> mehr nötig ist.Jens R. schrieb:> Und dabei ist es unerheblich wie diese aufgerufen (Pointer> vs weak) wird.
es ist für die Frage der Modularisierung ein riesiger Unterschied. Hat
STM erkannt und nachgebessert.
Die weak Funktion ist eine globale für alle Timer und z.B. genauso ist
es bei uart und anderen Stellen wo die ISR Callbacks nutzen. Da muss
also im low level Callback der Applikationscode aufgerufen werden, und
damit steht die Pyramide auf dem Kopf. Ich möchte für Modularisierung
einen low level Treiber haben den ich in die App einbinde und nicht
umgekehrt.
J. S. schrieb:> Jens R. schrieb:>> Und warum schreibt man dann nicht>> warum liest man das nicht?
Wenn man nicht schreibt um was es geht, bleibt nicht viel zum lesen 🙄
> Nein, den Murks braucht man nicht mehr.
Klingt nunmal wie die ganze Hetze gegen die HAL und nicht wie ein Aufruf
die veralteten weak gegen moderne Funktionspointer einzutauschen.
> J. S. schrieb:>> Bei den ISR hilft die HAL ja schon mit der neuen Struktur in der die>> callbacks einer Ressource zugeordnet werden und der weak Murks nicht>> mehr nötig ist.>> Jens R. schrieb:>> Und dabei ist es unerheblich wie diese aufgerufen (Pointer>> vs weak) wird.>> es ist für die Frage der Modularisierung ein riesiger Unterschied. Hat> STM erkannt und nachgebessert.> Die weak Funktion ist eine globale für alle Timer und z.B. genauso ist> es bei uart und anderen Stellen wo die ISR Callbacks nutzen. Da muss> also im low level Callback der Applikationscode aufgerufen werden, und> damit steht die Pyramide auf dem Kopf. Ich möchte für Modularisierung> einen low level Treiber haben den ich in die App einbinde und nicht> umgekehrt.
Verzeih mir, aber vom Aufruf ist hier doch null Unterschied 🤔
1
{
2
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
3
htim->OC_DelayElapsedCallback(htim);
4
htim->PWM_PulseFinishedCallback(htim);
5
#else
6
HAL_TIM_OC_DelayElapsedCallback(htim);
7
HAL_TIM_PWM_PulseFinishedCallback(htim);
8
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ }
Es wird doch immer eine Funktion aufgerufen welche im ISR Kontext läuft.
Dabei ist doch Schnuppe ob diese weak (und damit per Linker) oder per
Pointer eingehängt wird.
Also wo ist hier der Vorteil im Programmablauf 😕
Jens R. schrieb:> Also wo ist hier der Vorteil im Programmablauf
der Vorteil ist in der Organisation.
Bei der weak Variante wird ein und dieselbe Funktion für alle Timer
aufgerufen, obwohl die Timer ja für unterschiedliche Zwecke eingesetzt
werden. In die Funktion müssen jetzt also Abfragen rein für welchen
Timer der CB gerufen wurde und ob ich jetzt in die Komponente Dingdong
oder Blinkblink springen muss. Und in diesen Code muss ich jetzt
includes für Dingdong und Blinkblink einbauen. -> Treiber braucht
Appcode per include
Bei der anderen Variante habe ich meine Komponenten und sage denen
welchen Timer sie benutzen sollen. Dann kann die Komponente den Callback
zum Timer registrieren. -> App benutzt Treiber als include
Ich bin kein HAL Verweigerer, nur weak sollte man in shit umbenennen um
es deutlicher zu machen.
Noch variabler wird es wenn man das Feature der Cortex-M nutzt die
Vektortabelle ins RAM zu verlagern. Dann können die Komponenten die eine
ISR brauchen diese auch zur Laufzeit in der Initialiserung setzen. Aber
das unterstützt HAL nicht, ist interessanter wenn man Komponenten als
Objekt Instanzen zusammensetzt.
J. S. schrieb:> Jens R. schrieb:>> Also wo ist hier der Vorteil im Programmablauf>> Bei der anderen Variante habe ich meine Komponenten und sage denen> welchen Timer sie benutzen sollen. Dann kann die Komponente den Callback> zum Timer registrieren. -> App benutzt Treiber als include
Ah jetzt hat es bei mir klick gemacht 🙈
Da ich bisher im CAN Bereich unterwegs war, ist mir noch garnicht
aufgefallen dass verschiedene Callbacks in der gleichen Funktion geendet
haben 😯
Wobei beider USART ist das glaube auch so 🤔 Naja da war ich nur mal kurz
zu Besuch 🤐
> Ich bin kein HAL Verweigerer, nur weak sollte man in shit umbenennen um> es deutlicher zu machen.
Wie gesagt, dann wäre es weniger missverständlich, wenn man dann nicht
gleich mit "Motzsprüchen" daher kommt 🤷♂️
> Noch variabler wird es wenn man das Feature der Cortex-M nutzt die> Vektortabelle ins RAM zu verlagern. Dann können die Komponenten die eine> ISR brauchen diese auch zur Laufzeit in der Initialiserung setzen. Aber> das unterstützt HAL nicht, ist interessanter wenn man Komponenten als> Objekt Instanzen zusammensetzt
Ja dass STM die ISR immer durch eine Interpreterfunktion durch drückt,
macht die ganze ISR Vektortabelle zu nichte 😕
Wie man das trotz HAL schön einbinden kann, ist aber etwas das man
später lösen kann 🙂
PS: ich habe mir erlaubt dir mal ne PN zu schicken 🙂
BennyV schrieb:> Embedded-C-Anfänger-Problem: Habe mein erstes lauffähiges Programm für> den STM32, ein PWM mit DMA,...
Ach nö, das ist nur ein Problem derjenigen Leute, die als Allererstes
sich solche Programme wie Cube usw. installieren und dann meinen, sie
hätten damit das Programmieren gelernt.
Mach es anders. Ganz anders. Konzipiere deine Firmware zuerst, und zwar
so, daß du möglichst sauber zwischen den niederen Dingen wie
Schnittstellenansteuerungen und den höheren Dingen wie Algorithmen
trennst und dir sinnvolle Schnittstellen dazwischen ausdenkst. Und in
main gehört zwar ein Aufruf einer Initialisier-Funktion für so ein
peripheres Ding hinein, aber das ist eher sowas:
void MeinKarlheinz_Init(void);
und es gehören in main keine Typdefinitionen hinein, die evtl. woanders
auch gebraucht werden. Wenn du also so einen Flimmerstreifen mit WS2812
ansteuern willst, dann kommt der komplette Treiber dafür in eine
separate Quelle und von der werden nur die Funktionen exportiert, die
zum Einstellen aufgerufen werden sollen. Also sowas wie 'an', 'aus',
Farbe wählen usw.
Anfänger machen fast immer den Fehler, drauflos zu tippen und sich dabei
dabei die tollsten Dinge zu denken, wobei zumeist die sinnvolle Struktur
vergessen wird und sich ärgerliche Fehlfunktionen einstellen. Dann wird
nach dem Debugger gerufen, weil man meint, daß so ein Programm die
verkorkste Struktur der Firmware richten kann. Bei Leuten, die sich am
Anfang mit den STM32 befassen, kommt hinzu, daß die Hilfsprogramme von
ST darauf ausgelegt sind, die Benutzer zum einen von der Hardware
fernzuhalten und zum anderen an ST zu binden. Man kann sich drauf
einlassen, aber dann muß man auch die Konsequenzen tragen.
W.S.
J. S. schrieb:> Ich bin kein HAL Verweigerer, nur weak sollte man in shit umbenennen um> es deutlicher zu machen.
Ich denke, das Einführen von 'weak' ist ein typisches Verhalten von
C-Programmierern. Eigentlich wurde es nur zum Organisieren der
Vektortabelle bei den Cortexen erfunden und auch nur innerhalb der
Assemblerquelle für den Startup-Code. Man hätte es dabei bewenden lassen
können und 'weak' hätte nie in C Eingang gefunden. Aber man ist als
C-Programmierer ja so grandios, selbst den Startup-Code in C formulieren
zu wollen, auch wenn das nur unter allergrößten Verrenkungen geht. Und
anschließend wird gegrübelt, wie man so ein schönes neues Wort auch
anderweitig anbringen kann, um damit die Programmiersprache noch mehr zu
verhunzen/verkomplizieren.
W.S.
W.S. schrieb:> BennyV schrieb:>> Embedded-C-Anfänger-Problem: Habe mein erstes lauffähiges Programm für>> den STM32, ein PWM mit DMA,...>> Ach nö, das ist nur ein Problem derjenigen Leute, die als Allererstes> sich solche Programme wie Cube usw. installieren und dann meinen, sie> hätten damit das Programmieren gelernt.
schade. Dachte, dass ich das von mir meine, wollte ich gar nicht zum
Ausdruck bringen.
> Mach es anders. Ganz anders.
Das klingt schon mal überzeugend.
> Anfänger machen fast immer den Fehler, drauflos zu tippen und sich dabei> dabei die tollsten Dinge zu denken, wobei zumeist die sinnvolle Struktur> vergessen wird und sich ärgerliche Fehlfunktionen einstellen.
aber genau darüber ist sich der Anfänger doch im Klaren und die
sinnvolle Struktur zu finden ist doch des Anfängers Frage hier!
Wie bist du eigentlich als Anfänger vorgegangen? Die Antwort erspart mir
bestimmt viel Ärger.
Hi.
Die gleiche Frage habe ich mir auch schon öfter gestellt. Das beste was
ich bis jetzt finden konnte ist die Handler, z.B. htim in die module zu
ubergen - läuft aber dann nur auf STMs.
Gruß
BennyV schrieb:> Wie bist du eigentlich als Anfänger vorgegangen?> Die Antwort erspart mir bestimmt viel Ärger.
Wohl eher nicht. Ich hatte mit mittlerer
Datentechnik/Prozeßrechentechnik angefangen. Also mit RTOS und in
Assembler und nicht mit Mikrocontrollern.
Nun, ich hab hier ja seit langem den Mikrocontroller-Anfängern
gepredigt, daß man in seiner Firmware die Ebenen trennen soll, also
Low-Level-Angelegenheiten wie z.B. Treiber für die Peripherie auf dem
Chip oder Ansteuerungen für Hardware außerhalb des Controllers wie z.B.
Steuersignale trennen von den Algorithmen und anderen High-Level-Dingen.
Aber die Leute wie du sind ja so unendlich schlau, daß sie Ratschläge
dieser Art nicht benötigen und stattdessen Programme wie eben Cube und
Konsorten lieben, wo man sich sämtliche Lowlevel-Angelegenheiten in
main() hereinholt und dann wird gefragt, wie man sowas hinterher
modularisieren kann. Da kann ich nur noch antworten: indem man anders
anfängt und auch anders weitermacht. Also wie geschrieben, ganz anders.
So, hilft dir das? Oder ärgerst du dich jetzt darüber, daß dir jemand
sagt, daß du es anders machen mußt, wenn du es anders haben willst?
W.S.
W.S. schrieb:> Aber die Leute wie du sind ja so unendlich schlau, daß sie Ratschläge> dieser Art nicht benötigen und stattdessen Programme wie eben Cube und> Konsorten lieben, wo man sich sämtliche Lowlevel-Angelegenheiten in> main() hereinholt und dann wird gefragt, wie man sowas hinterher> modularisieren kann. Da kann ich nur noch antworten: indem man anders> anfängt und auch anders weitermacht. Also wie geschrieben, ganz anders.
Naja, die main ist nunmal jene Funktion die bei den meißten
Implementierungen als erste Funktion jenseits des Assembler angesprungen
wird.
Also sollte die Initialisierung (so fern diese nicht in Assembler
geschrieben ist) auch von der main aus ausgerufen werden.
Und genau das macht der von ST erzeugte Code. Dieser trennt sauber
zwischen Hardware naher Programmierung und jenem Bereich den der
Entwickler betreten darf.
Will man auf Hardware nahe Sachen zu greifen, ruft man die HAL
Funktionen auf.
Das man eine Struktur und/oder einen Handle braucht um die jeweilige
Hardware Komponente zu adressieren lässt sich in C nur bedingt
vermeiden.
Es steht einem aber offen oberhalb der HAL (die ja Hardware Abstraction
Layer heißt) noch eine System-unabhängige Interface Ebene zu
Implementieren.
Also sorry, das Schimpfen gegen Cube und Co zeigt nur, dass du dich
nicht in die Gedankenwelt eines anderen hein versetzen kannst und nur
deine eigene Welt siehst.
Achja, wenn ein Anfänger nach Ratschlägen fragt, dann muss man ihn nicht
anpöpeln, das er keine Ratschläge annehmen würde 😅
Jens R. schrieb:> Naja, die main ist nunmal jene Funktion die bei den meißten> Implementierungen als erste Funktion jenseits des Assembler angesprungen> wird.
Das löst nicht das Problem, daß man dort nicht alles hineinwerfen
sollte. Auch wenn einem solche Programme per Kommentar
/* ab hier dein Zeugs reinschreiben */
nahelegen, wo man denn mit dem Schreiben anfangen sollte. Tja und wenn
jemand erstmal angefangen hat, das als gegeben zu sehen um sich dann von
einer Biblitheksfunktion zu anderen zu hangeln - in main versteht sich -
dann ist der Wunsch nach anschließender funktionaler Aufteilung so
ziemlich vergeblich, weil er das so ziemliche Abreißen alles bisher
Aufgebautem bedeutet. Nichts ist separierbar und woanders
wiederverwendbar. Von daher mein Rat, es komplett anders zu machen.
Und ja, ich weiß, das so etwas erstmal mühsam ist, genau so wie der
Umstieg von der Büchse mit Fertigessen aus dem Laden auf Selberkochen.
Nun ja, die Produzenten von Büchsenessen wollen auch leben, genauso wie
ST eben auch.
W.S.
W.S. schrieb:> Jens R. schrieb:>> Naja, die main ist nunmal jene Funktion die bei den meißten>> Implementierungen als erste Funktion jenseits des Assembler angesprungen>> wird.>> Das löst nicht das Problem, daß man dort nicht alles hineinwerfen> sollte.
Ist aber im generierten Code das gleiche wie im selbst geschrieben und
hat null Komma nix mit ST oder dem Cube-System zu tun.
Nur warum wird es dann von dir verteufelt?
> Auch wenn einem solche Programme per Kommentar> /* ab hier dein Zeugs reinschreiben */> nahelegen, wo man denn mit dem Schreiben anfangen sollte.
Ok. Wenn
1
2
/* User section begin */
3
4
/* User section end */
für dich bedeuten dass da alles rein muss und nicht evtl ein Aufruf oder
Sprung in den eigenen Code, dann ist das doch dein Problem und nicht der
von der Umgebung 🤔
Auch in deiner Architektur wirst du in der main irgendwo anders
hinspringen. Mikrocontroller Code läuft nunmal immer in einer
Endlosschleife. Es steht einen frei ob man in der vorgefertigten while
Schleife einen eigenen Scheduler implementiert oder ob man sich eins von
den konfigurierbaren OS auswählt. Und genau hier hat die Cube-Umgebung
einen riesen Vorteil, nämlich in dem man ein OS nutzen kann, welches gut
getestet ist (ja ich weiß man muss auch noch lernen es richtig zu
konfigueren).
> Tja und wenn> jemand erstmal angefangen hat, das als gegeben zu sehen um sich dann von> einer Biblitheksfunktion zu anderen zu hangeln - in main versteht sich -> dann ist der Wunsch nach anschließender funktionaler Aufteilung so> ziemlich vergeblich, weil er das so ziemliche Abreißen alles bisher> Aufgebautem bedeutet. Nichts ist separierbar und woanders> wiederverwendbar. Von daher mein Rat, es komplett anders zu machen.> Und ja, ich weiß, das so etwas erstmal mühsam ist, genau so wie der> Umstieg von der Büchse mit Fertigessen aus dem Laden auf Selberkochen.> Nun ja, die Produzenten von Büchsenessen wollen auch leben, genauso wie> ST eben auch.
Jeder Programmierer hat mit einem "Hello World" Programm angefangen. Und
hat dort erst einmal auch die main aufgebläht.
Erst wenn man ein Grundgefühl für Abläufe entwickelt hat, macht es auch
Sinn sich mal eine kleine Architektur auszudenken. Und genau das vom TO
beschriebene Problem ist meiner Meinung nach prima geeignet sich an
einem Treiber für die NeoPixel-Streifen zu versuchen. Aber der TO wird
in seinem ersten Anlauf wohl noch nicht dran denken, die
Registerschreiberei (bzw die HAL Kapselung) entsprechend zu separieren.
Das gehört zum Lernprozess und ist dank der Zwischenablage auch
nachträglich machbar.
Jens R. schrieb:> Ist aber im generierten Code das gleiche wie im selbst geschrieben und> hat null Komma nix mit ST oder dem Cube-System zu tun.> Nur warum wird es dann von dir verteufelt?
Ich verteufele nichts. Wer nur Blinkprogramme schreiben und nur bei
STM32 bleiben will und das nur zum Hobbygebrauch, der mag das nach
seinem Gusto tun. Aber dann jammern, wenn es ihm selber nicht mehr
gefällt oder Probleme macht, gilt nicht. Der einzige Rat, der dann
helfen würde, nämlich alles einzureißen und anders zu machen, wird dann
als Affront empfunden, siehe dieser Thread hier.
Und deine Bemerkung, daß es "das gleiche wie im selbst geschrieben" sei,
stimmt nur dann, wenn man es genauso tut wie Cube. Sonst nicht. Du hast
da ganz offensichtlich einen recht speziellen Blickwinkel, der dich auch
das Folgende schreiben und denken läßt:
Jens R. schrieb:> Jeder Programmierer hat mit einem "Hello World" Programm angefangen.
Wirklich jeder? Nö.
W.S.
W.S. schrieb:> Aber die Leute wie du sind ja so unendlich schlau, daß sie Ratschläge> dieser Art nicht benötigen und stattdessen Programme wie eben Cube und> Konsorten lieben, wo man sich sämtliche Lowlevel-Angelegenheiten in> main() hereinholt und dann wird gefragt, wie man sowas hinterher> modularisieren kann. Da kann ich nur noch antworten: indem man anders> anfängt und auch anders weitermacht. Also wie geschrieben, ganz anders.
aha. Im Cube Code stehen zu Anfang von main() Aufrufe von Funktionen die
die verwendete Hardware initialisieren, was ist schlecht daran? Die
meisten Funktionen sind hinten im main.c, was ist schlecht daran?
Und wenn man es nicht so mag, dann setzt man das Häkchen bei 'Generate
perihperial initialization as pair of .c/.h files per peripheral'. Und
schon sind die Funktionen aus main.c verschwunden und in eigenen
Dateien. Sogar mit konsistenter Namensgebung und man muss nicht
V24_init() in serial.h suchen. Auch kein Denglisch, oder soll das auch
besser sein?
Im main.c direkt auf HW Register zugreifen mit Magic Numbers, ist das
die vielzitierte Abstraktion von HW?
if (PWMMCR != 3)...
while (T0TC < 3000);...
W.S. schrieb:> Ich verteufele nichts. Wer nur Blinkprogramme schreiben und nur bei> STM32 bleiben will und das nur zum Hobbygebrauch, der mag das nach> seinem Gusto tun.
CubeMX läuft nicht nur im Hobbybereich. Ich habe eine Kiste mit 17x H7
vor mir und die Entwickler haben das nicht aus Spaß an der Freud gebaut.
W.S. schrieb:> Jens R. schrieb:>> Ist aber im generierten Code das gleiche wie im selbst geschrieben und>> hat null Komma nix mit ST oder dem Cube-System zu tun.>> Nur warum wird es dann von dir verteufelt?>> Ich verteufele nichts. Wer nur Blinkprogramme schreiben und nur bei> STM32 bleiben will und das nur zum Hobbygebrauch, der mag das nach> seinem Gusto tun.
Und warum muss ein NeoPixel-Treiber, den man in der Cube Umgebung
geschrieben hat unbedingt nur für STM32 sein? Der lässt sich auch in der
Umgebung ausreichen kapseln.
> Aber dann jammern, wenn es ihm selber nicht mehr> gefällt oder Probleme macht, gilt nicht. Der einzige Rat, der dann> helfen würde, nämlich alles einzureißen und anders zu machen, wird dann> als Affront empfunden, siehe dieser Thread hier.
Der einziger der hier jammert bist du. Der TO fragt nach Auswegen. Und
die gibt es. Dazu muss er aber lernen wie er die Hardware kapselt. Und
das muss er lernen egal ob es in der Cube Umgebung oder ob es in seinem
selbst gestrickten Flickenteppich ist.
> Und deine Bemerkung, daß es "das gleiche wie im selbst geschrieben" sei,> stimmt nur dann, wenn man es genauso tut wie Cube. Sonst nicht.
Ach wie kommst du dann von der main in deinene abstrahierten Module? 🤔
> Du hast da ganz offensichtlich einen recht speziellen Blickwinkel, der dich auch> das Folgende schreiben und denken läßt:>> Jens R. schrieb:>> Jeder Programmierer hat mit einem "Hello World" Programm angefangen.
Och ja kann man tatsächlich als speziellen Blickwinkel nennen 🙂.
In dem Fall halt, das der Blickwinkel nicht in enge Mauern gepresst ist.
So gibt es eben verschiedene "Hello World" Programme. Auf einem PC fängt
man mit einfachen Textausgaben an. Auf einem kleinen uC fängt man mit
Pinwackeln an. Ich muss gestehen nicht mehr zu wissen, was wir auf dem
KC als ersten Versuch gemacht haben 🙈 aber es war eben auch etwas in der
Richtung "hallo hier bin ich"
> Wirklich jeder? Nö.
Also, doch, jeder. Oder war dein erstes Programm gleich mehrere Kilobyte
groß?
Fiktive Auftragsverlauf:
Kunde:
Hallo Herr W.S., wir brauchen eine Firmware für eine Display-Einheit,
die man per USB an unsere Wärnmepumpen anstöpseln kann. Die Hardware ist
bereits fertig, und wir suchen jemand, der die erforderliche Firmware
entwickelt.
Wäre das was für Sie?
W.S.:
Ja sicher, kein Problem!
Nur Anzeigen?
Und um wie viele Werte handelt es sich?
Kunde:
Ja, nur anzeigen, und es sind 6 Parameter, die angezeigt werden sollen.
W.S.
Klingt nach einem lösbaren Problem.
Wie schnell brauchen Sie das?
Kunde:
Wir hatten uns 6 Wochen als Zeitrahmen vorgestellt.
Nennen Sie bitte mal eine Größenordnung um den Kosten-Rahmen
abzustecken!
W.S.:
<kurze Pause> ...also so um 10.000€ sollte das machbar sein.
***
Die Beiden werden sich handelseinig, und die Uhr startet bei t-6
Wochen....
***
Woche 5
Kunde:
Hallo Herr S., wie schauts denn aus?
W.S.:
Alles gut, nur die USB-Hardware von dem Chip, den Sie nutzen macht
Ärger, aber, ich bin dem Problem auf der Spur.
Nach Ablauf der 6 Woche:
Kunde:
Sind Sie fertig?
W.S.:
Leider nicht - der USB-Port ist wirklicher Mist - die Ingenieure von XXX
sollte man teeren und federn - aber ich bekomm das in den Griff!
Geben Sie mir noch 1 Woche!
Nach 7 Wochen
Kunde:
Und? Lauft das Ding?
W.S.:
<freudig> Ja!
Ich hab das USB-Problem gelöst, aber jetzt zickt das Display.
Warum zum Teufel braucht man zur Anzeige von 6 Werten ein Grafik-Display
mit 480x320 Pixel?
Ein 2x20 Text-Display wäre viel passender!
...to be continued.
Fazit: Mit ordentlicher Hersteller-Unterstützung (aka HAL) wär das nicht
passiert :-D
W.S. schrieb:> So, hilft dir das? Oder ärgerst du dich jetzt darüber, daß dir jemand> sagt, daß du es anders machen mußt, wenn du es anders haben willst?>> W.S.
Habe mich keinseswegs darüber geärgert, etwas neues aufgezeigt zu
bekommen oder über mögliche grundsätzliche Fehler meines ersten Ansatzes
aufgeklärt zu werden. Aber dein herablassender Ton kombiniert mit einer
fragwürdigen Eindimensionalität der Antwort (so und nicht anders) muss
man nicht unbedingt freundlich empfangen. Etwas im Bereich
Wissenskommunikation hinzuzulernen würde deinem professionellen
Erscheinungsbild nicht schaden. Du würdest dir auch selbst einen
Gefallen tun, da Anfänger bei einer freundlicheren Ansprache weitaus
aufnahmebereiter sein werden und du weniger Ärger über all diese
"unbelehrbaren Idioten" in dich hineinfressen müsstest.
J. S. schrieb:> Bei der anderen Variante habe ich meine Komponenten und sage denen> welchen Timer sie benutzen sollen. Dann kann die Komponente den Callback> zum Timer registrieren. -> App benutzt Treiber als include
Gibt es da mal irgendein triviales Beispiel (z.B. Blinky mit Timer
Output Compare Interrupt)? Trotz 20 Minuten Suche konnte ich nichts
finden, wie man das dann praktisch umsetzt.
Habe jetzt das hier gefunden, aber mit DMA:
https://blog.softwareentwicklung-als-prozess.de/cpu-zeit-sparen-mit-direct-memory-access
Das macht es schon etwas verständlicher:
- Beim ersten Erscheinen von UART_DMA_TransferComplete handelt es sich
um einen Prototyp?
- Beim zweiten Erscheinen wird der Callback registriert?
- Beim dritten Erscheinen wird der Callback (die Funktion) definiert?
BennyV schrieb:> Aber dein herablassender Ton kombiniert mit einer> fragwürdigen Eindimensionalität der Antwort (so und nicht anders) muss> man nicht unbedingt freundlich empfangen.
Du verwechselst gerade etwas: Ich bin nicht derjenige, welcher grad nach
Hilfe gerufen hat.
Und wenn man etwas anders haben will, dann muß man es eben anders
machen. Also nicht "so und nicht anders" sondern "wenn anders dann
anders und nicht so wie gehabt".
Ich habe meinerseits den Eindruck, daß viele Anfragende meinen, dieses
Forum sei sowas wie ein Cola-Automat: Oben ne Münze rein und es hat
daraufhin eine Flasche Cola (bzw. eine dem Fragesteller gefallende
Antwort) unten herauszukommen. So etwas empfinde ich als ausgesprochen
anmaßend und dreist.
Also nochmal: Wenn man sowas wie Cube (oder dessen Gegenstücke bei
anderen Herstellern) benutzen will, dann soll man auch gefälligst die
Konsequenzen daraus akzeptieren - oder es eben anders machen, wie ich es
vorgeschlagen habe.
W.S.
W.S. schrieb:> Du verwechselst gerade etwas: Ich bin nicht derjenige, welcher grad nach> Hilfe gerufen hat.
Es hat auch keiner nach deinen verschobenen Ansichten gesucht.
Es lässt sich alles im Rahmen der Frage lösen. Wenn man es denn
versteht.
J. S. schrieb:> Es lässt sich alles im Rahmen der Frage lösen. Wenn man es denn> versteht.
Das passt aber nicht in das Weltbild des W.S..
Übrigens sollte man bei "Cube" unterscheiden:
STM32CubeMX ist ein Wizard, um die Hardware eines STM32 (abstrakt) zu
initialisieren.
STM32CubeIDE ist eine auf Eclipse basierende Entwicklungsumgebung, die
STM von Attollic übernommen und auf STM-Controller beschnitten hat.
Leute wie W.S. verwenden vermutlich noch "vi" und Magic Numbers - selbst
ein Makro wäre ja schon eine Abstrahierung, die nicht in deren Weltbild
passt.
Hört sich gut an schrieb:> Habe jetzt das hier gefunden, aber mit DMA:> https://blog.softwareentwicklung-als-prozess.de/cpu-zeit-sparen-mit-direct-memory-access> Das macht es schon etwas verständlicher:> - Beim ersten Erscheinen von UART_DMA_TransferComplete handelt es sich> um einen Prototyp?> - Beim zweiten Erscheinen wird der Callback registriert?> - Beim dritten Erscheinen wird der Callback (die Funktion) definiert?
Hm, bin gerade wieder zu dem Problem zurückgekehrt und verstehe den
Vorteil mit RegisterCallback schon. Aber hilft mir konkret irgendwie
nicht weiter:
1
voidTIM2_IRQHandler(void)
2
{
3
/* USER CODE BEGIN TIM2_IRQn 0 */
4
5
/* USER CODE END TIM2_IRQn 0 */
6
HAL_TIM_IRQHandler(&htim2);
7
/* USER CODE BEGIN TIM2_IRQn 1 */
8
9
/* USER CODE END TIM2_IRQn 1 */
10
}
Das wird ja immer aufgerufen und in
HAL_TIM_IRQHandler(&htim2);
werden die Callbacks ausgewertet. Nur leider werden dort gleich zu
Beginn auch die Interruptflags gelöscht. Wenn ich nun z.B. einen Output
Compare Channel auswerten will, weiß ich in meinem Callback ja schon gar
nicht mehr, welches Flag/Channel ausgelöst hat. Ich müßte also davor in
der User Section die Interruptflags auslesen, um sie im Callback
verarbeiten zu können. Ist nach meinem Verständnis irgendwie nicht
modular und ich könnte gleich meine komplett eigene ISR in die erste
User Section schreiben und noch vor HAL_TIM_IRQHandler(&htim2); mit
return wieder raus, um diesen dann unnötigen Aufruf zu vermeiden.
Da mache ich doch garantiert wieder einen riesigen Gedankenfehler?
Der auslösende Channel wird in htim->Channel gespeichert. Der Callback
wird mit htim als Argument aufgerufen, also alles nötige vorhanden.
Das ist der Vorteil das alles in Strukturen gespeichert ist und damit
z.B. auch die ganzen Init Werte noch bekannt sind.
Hört sich gut an schrieb:> Ist nach meinem Verständnis irgendwie nicht> modular und ich könnte gleich meine komplett eigene ISR in die erste> User Section schreiben
da die ISR weak deklariert ist, kann man die auch komplett ersetzen.
Oder zur Laufzeit durch Verschieben der Vectortable ins RAM setzen. Sehr
praktisch wenn man mit (C++)Komponenten arbeitet.
J. S. schrieb:> Der auslösende Channel wird in htim->Channel gespeichert. Der Callback> wird mit htim als Argument aufgerufen, also alles nötige vorhanden.> Das ist der Vorteil das alles in Strukturen gespeichert ist und damit> z.B. auch die ganzen Init Werte noch bekannt sind.
Argh!!! Ich Blindfisch! Das habe ich glatt überlesen. Jetzt hab ich's.
Danke!
> Oder zur Laufzeit durch Verschieben der Vectortable ins RAM setzen. Sehr> praktisch wenn man mit (C++)Komponenten arbeitet.
Da bräuchte ich noch einen kleinen Link, um auch das zu raffen. VTOR
gibt es aber, glaube ich, erst ab M3. Aber das ist vermutlich nur
Nebensache.
Stefan F. schrieb:> W.S. verteufelt jeden Programmierstil, der von seinem eigenen abweicht.
Nun laßt doch bitte dieses Rumgetrete. Es bringt überhaupt nix und die
Ansichten des einen oder anderen Users sind ja nun wirklich hinlänglich
bekannt.