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 😀
schreibe oben dein c-file einmal
1 | #include "main.h" |
rein
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.
:
Bearbeitet durch User
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.
1 | volatile uint8_t datasent = 0; |
2 | |
3 | void Display_Send(void) { |
4 | ...
|
5 | |
6 | HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_4, (uint32_t*) pwmData, indx); |
7 | while (!datasent) { |
8 | };
|
9 | datasent = 0; |
10 | }
|
11 | |
12 | void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) { |
13 | HAL_TIM_PWM_Stop_DMA(htim, TIM_CHANNEL_4); |
14 | datasent = 1; |
15 | }
|
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.
Du kannst die Callback Funktion doch in deinem Source Code umsetzen.
1 | void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) { 13 HAL_TIM_PWM_Stop_DMA(htim, TIM_CHANNEL_4); 14 datasent = 1; 15} |
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.
Jens R. schrieb: > Du kannst die Callback Funktion doch in deinem Source Code umsetzen. >
1 | void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) { 13 |
2 | > HAL_TIM_PWM_Stop_DMA(htim, TIM_CHANNEL_4); 14 datasent = 1; 15} |
> > 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: > Na klar kann es die HAL_TIM_PWM_PulseFinishedCallback auch nur > einmal > geben. Nein, den Murks braucht man nicht mehr. https://github.com/STMicroelectronics/STM32CubeF4/blob/52757b5e33259a088509a777a9e3a5b971194c7d/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c#L3829 https://github.com/STMicroelectronics/STM32CubeF4/blob/52757b5e33259a088509a777a9e3a5b971194c7d/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c#L3840
:
Bearbeitet durch User
J. S. schrieb: > Nein, den Murks braucht man nicht mehr. Na das ist doch mal ein sehr hilfreicher Beitrag. Dir ist aber schon klar, wenn man die Interruptroutinen selber einhängen möchte, dass man dann die gesamte HAL raus schmeißen muss 🙄 Und wenn jemand zu beginn mit der HAL und all den Vorzügen einer automisch generierten Basissoftware arbeiten möchte, dann kommt man nunmal bei ST (wie bei vielen anderen auch) nicht um die HAL des Herstellers herum. > https://github.com/STMicroelectronics/STM32CubeF4/blob/52757b5e33259a088509a777a9e3a5b971194c7d/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c#L3829 > > https://github.com/STMicroelectronics/STM32CubeF4/blob/52757b5e33259a088509a777a9e3a5b971194c7d/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c#L3840 Und warum verlinkst du zwei mal die gleiche Datei? Die 10 Zeilen könnte man auch scrollen 🤔
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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: > Ich hatte mit mittlerer > Datentechnik/Prozeßrechentechnik angefangen. Und warum hast du aufgegeben dazuzulernen?
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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
:
Bearbeitet durch User
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 | void TIM2_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.
:
Bearbeitet durch User
Jens R. schrieb: > Nur warum wird es dann von dir verteufelt? W.S. verteufelt jeden Programmierstil, der von seinem eigenen abweicht.
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.
Die Vectoren zu setzen ist Aufgabe der CMSIS, da gibt es NVIC_SetVector(): https://www.keil.com/pack/doc/CMSIS/Core/html/group__NVIC__gr.html#gab43c1c59d5c081f1bc725237f4b1f916 Und da gibt es auch einen weiterführenden Link wie das Kopieren ins RAM aussieht.
Besten Dank, der Tag hat sich für mich wieder gelohnt :-) Und auch noch rausgefunden, daß mein Liebling LPC845 als M0+ auch VTOR implementiert hat.
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.