Versuche meinen Code besser zu modularisiren. Konkret habe ich einen STM32F303 und daran an einem I2C Bus zwei verschiedene Sensoren. (CubeIDE mit HAL) Überlege mir nun wie ich das generell am besten löse wenn ich mehrere Module (je eines für die verschiedenen Sensoren) mache, aber am Schluss immer der gleiche Interupt ausgelöst wird wenn die Übertragung fertig ist. Also wie kann ich nach dem Interrupt bestimmen welche Funktion nun die Daten braucht? Die KI schlägt mir da einen Dispatcher vor der dann die entsprechende Funktion nach einer registrierung aufruft vor. Bin aber noch relativer Anfänger, ist das ein guter Ansatz oder wie macht man das üblicherweise? Danke.
Wenn ich aus mehreren Modulen auf eine Peripherie zugreifen möchte, will ich natürlich auch verhindern, dass ein Modul X auf die Hardware zugreift, da Modul Y z.B. noch keine Antwort erhalten hat. Ich habe mir dazu einen Job-Manager geschrieben. Dieser nimmt Jobs entgegen, reiht diese ein und arbeitet diese ab. Natürlich muss jedes Modul das einen Job übergibt auch eine Callback Funktion mitliefern. Im Endeffekt, ruft die Hardware den Interrupt auf, dieser dann meinen Job-Manager und dort sind ja die jeweiligen Callbacks für den aktuellen und noch offene Jobs bekannt. Ob diese Lösung auch für dich passt, kann ich nicht beurteilen. Aber es gibt da bestimmt 100te Wege die ans Ziel führen. Man könnte dies wohl als "Dispatcher" betrachten, so wie du es erwähnt hast.
:
Bearbeitet durch User
Samuel schrieb: > CubeIDE mit HAL Bei der HAL kannst du doch meistens einen Callback übergeben, was du zuerst in STM32CubeMX im Project Manager aktivieren musst. Dann ruft die HAL automatisch den richtigen Callback des Moduls auf, das eine Aktion angefordert hatte.
Niklas G. schrieb: > Bei der HAL kannst du doch meistens einen Callback übergeben, was du > zuerst in STM32CubeMX im Project Manager aktivieren musst. Dann ruft die > HAL automatisch den richtigen Callback des Moduls auf, das eine Aktion > angefordert hatte. Tja, ist leider ziemlich blöd gelöst, weil das Konzept von Hause aus nur einen Callback-"Konsumenten" erlaubt. Das ist gerade für Sachen wie ISP oder I2C mit vielen verschiedenen, ggf. sogar ziemlich komplexen Clients ziemlich schlecht für die Code-Modularisierung. Und wenn deren Existenz dann auch noch "dynamisch" ist, also erst zur Laufzeit ermittelt werden kann, ob die zugehörige Hardware des Clients überhaupt im System ist, scheitert das Konzept endgültig. Also für alles, was näherungsweise nach einem Bus-System aussieht, taugt der Ansatz von CubeMX leider nix. Läuft i.A. dann darauf hinaus, dass man eine eigene Zwischenschicht mit eigenem Client-API einziehen muss.
Ob S. schrieb: > Tja, ist leider ziemlich blöd gelöst, weil das Konzept von Hause aus nur > einen Callback-"Konsumenten" erlaubt Bei I2C kann sowieso nur eine Kommunikation gleichzeitig statt finden, ich kann mir keinen Anwendungsfall vorstellen wo zwei Module gleichzeitig über I2C kommunizieren müssen - nur abwechselnd. Jedes Modul setzt direkt vor dem Start des Transfers den eigenen Callback via HAL_I2C_RegisterCallback.
Niklas G. schrieb: > Jedes > Modul setzt direkt vor dem Start des Transfers den eigenen Callback via > HAL_I2C_RegisterCallback. Also könnte ich damit folgendes machen: 1. HAL_I2C_RegisterCallback für Sensor A 2. Abfrage starten 3. Callback ruft meine Funktion in Modul A auf die den Sensor Wert verarbeitet. 4. HAL_I2C_RegisterCallback für Sensor B 2. Abfrage starten 3. Callback ruft meine Funktion in Modul B auf die den Sensor Wert verarbeitet. Damit könnte ich ja relativ elegant die Funktionen zum Empfangen/Auswerten der Sensordaten in den entsprechenden Modulen lassen? Wäre das der bevorzugte Weg um mehrere I2C Sensoren auszuwerten? Oder gibt es da bewährtere Wege?
Samuel schrieb: > Also könnte ich damit folgendes machen: Ja ganz genau so. Samuel schrieb: > Oder gibt es da bewährtere Wege? Im Prinzip funktionieren ja viele APIs/Frameworks aus allen möglichen Bereichen genau so - oft übergibt man den Callback direkt an die Funktion, welche die Operation startet, hier gibt es eine separate Funktion zum Setzen des Callbacks. Ich glaub der verbreitetste Weg ist es ein RTOS zu nutzen, und synchrone/blockierende Aufrufe für I2C zu nehmen und gar keine Callbacks zu setzen. Hat auch seine Vor- und Nachteile. Bei asynchroner Programmierung mit vielen Callbacks kommt man schnell mal in die "Callback Hell" - in anderen Programmiersprachen kann man das mit async/wait, Coroutinen, Futures, Reactive Flows strukturieren, bei Embedded hat man weniger Möglichkeiten. Dafür sind bei Embedded die Abläufe meist nicht so kompliziert. Daher ist die Verwendung von Callbacks IMO durchaus sinnvoll, wenn man den Code gut strukturiert. In C++ kannst du als Funktionszeiger für den Callback ein Lambda ohne Captures übergeben, dann steht der Callback-Code immerhin in der Nähe des Starts der Operation.
Je mehr Module diesen Sensor abfragen, umso mehr Kommunikation und Warten auf die Antwort findet statt. Das kann je nach Anwendungsfall schnell zum Flaschenhals führen, wenn nicht gar Deadlocks. Ich meine hier gelesen zu haben, daß eine beliebte RTC und ein beliebter Temperaturfühler bei zu vielen Anfragen sogar zu Fehlfunktionen neigen. Also ist es vielleicht besser, den Sensor in regelmäßigen Intervallen zu pollen und das Ergebnis über shared Memory (bzw. entsprechende Getter) den Modulen bereit zu stellen.
:
Bearbeitet durch User
Nemopuk schrieb: > Also ist es vielleicht besser, > den Sensor in regelmäßigen Intervallen zu pollen und das Ergebnis über > shared Memory (bzw. entsprechende Getter) den Modulen bereit zu stellen. Es sind doch Samuel schrieb: > zwei verschiedene Sensoren an einem einzelnen I²C-Bus, und jedes Modul kann für sich sicherstellen, dass es "seinen" Sensor nicht zu oft abfragt. Außer jedes Modul fragt beide Sensoren ab...
Ach so. Da habe ich wohl den Anwendungsfall missverstanden.
Samuel schrieb: > Versuche meinen Code besser zu modularisiren. Konkret habe ich einen > STM32F303 und daran an einem I2C Bus zwei verschiedene Sensoren. > (CubeIDE mit HAL) Verstehe dein Problem nicht. Sensoren können von sich aus keine Kommunikation starten. Master fragt, Slave antwortet - was ist da zu modularisieren?
Marc V. schrieb: > Master fragt, Slave antwortet - was ist da zu modularisieren? Sorry für die späte Antwort, habe das übersehen. Möchte mein Code so strukturieren, das ich ein Modul (Header und Source File) für Sensor A und Sensor B habe. Wo ich die komplette Auswertung und Konfiguration des Sensors habe. Mein Problem ist nun das ich das abfragen starte (Sensor A) und dann einen Interrupt bekommen wenn die Daten/Antwort des Sensors bereit ist. Nun weiss ich aber nicht wie ich die Daten verarbeite, da ich ja nicht weiss welches Modul das gestartet hat. Das nicht wissen ist hier natürlich etwas konstruiert, bei diesem einfachen Fall kann man das sicher lösen. Möchte aber das ganze allgemein lösen, das ich in Zukunft belibige Sensoren immer gleich einbinden kann. Ich für den Sensor quasi nur noch das passende Header File einbinden muss und dann alles ohne weitere Logik im Hauptprogramm funktioniert.
Samuel schrieb: > Nun weiss ich aber nicht wie ich die Daten verarbeite, da ich ja nicht > weiss welches Modul das gestartet hat. War die Frage denn jetzt nicht schon beantwortet? Hat es mit HAL_I2C_RegisterCallback nicht funktioniert?
Doch eigentlich schon, wollte nur auf Marc V. antworten. Vielleicht mache ich es ja einfach nur zu kompliziert. Vielen Dank dir für deine Hilfe.
Mach doch einfach eine globale Variable: Wert 0 = Nix zu tun 1 = Sensor 1 abfrage 2 = Sensor 1 hat geantwortet 3 = Sensor 2 abfragen 4 = Sensor 2 hat geantwortet Ja, ich weiß, globale Variablen sind verpönt. Aber es ist eine sehr gute Möglichkeit, um zwei Tasks zu synchronisieren, bzw. Informationen auszutauschen.
:
Bearbeitet durch User
Dirk F. schrieb: > Ja, ich weiß, globale Variablen sind verpönt ganz besonders dann, wenn man von mehreren Tasks gleichzeitig drauf zugreift! Absolutes "recipe for disaster".
Samuel schrieb: > Möchte aber das ganze > allgemein lösen, das ich in Zukunft belibige Sensoren immer gleich > einbinden kann. Ich für den Sensor quasi nur noch das passende Header > File einbinden muss und dann alles ohne weitere Logik im Hauptprogramm > funktioniert. Auftrag, Job, Taskmanager, die Namen sind verschieden, doch es läuft m.e. auf Adams Lösung hinaus. Adam P. schrieb: > Ich habe mir dazu einen Job-Manager geschrieben. > Dieser nimmt Jobs entgegen, reiht diese ein und arbeitet diese ab. Bei i2c kann so ein Job ein einfaches Telegram mit Antwort sein. Also Adresse, Datenbyte(s), Platz für die Antwort. Optional Prio, Auftragsnummer, Wartezeit, Empfangsqueue .... was auch immer. Die Ausgestaltung hängt von Deiner generellen Architektur ab. Arbeitest Du mit parallelen Tasks, kann die Funktionen blockierend sein > n = I2Cjob(addr, cmd, *data) Oder in einer Empfangs-Queue auflaufen Details machen nur Sinn, wenn wir eine grobe Vorstellung Deines "Systems" haben, sonst hat jeder was anderes im Kopf.
Bruno V. schrieb: > Arbeitest Du mit parallelen Tasks, kann die Funktionen blockierend sein Soll sie ja nicht, er hat explizit nach Callbacks gefragt. Und genau dieses Problem löst so ein Task Scheduler erstmal nicht. Rein zufällig enthält die HAL aber bereits die Lösung in Form eines einstellbaren Callbacks. Könnte man auch als Observer-Pattern oder async-wait betrachten. Der STM32F303 ist auch ein Single-Core, der kann nichts parallel ausführen.
Samuel schrieb: > Mein Problem ist nun das ich das abfragen starte (Sensor A) und dann > einen Interrupt bekommen wenn die Daten/Antwort des Sensors bereit ist. > Nun weiss ich aber nicht wie ich die Daten verarbeite, da ich ja nicht > weiss welches Modul das gestartet hat. Doch weißt du, denn irgendwas - was du programmiert hast - ruft das Modul auf um den Sensor abzufragen. Die Module werden nicht durch Zauberhand aufgerufen. Da es keine gute Idee ist überlappende Abfragen/Antworten auf I2C zu haben (kann I2C nicht), ist mit Start der Abfrage klar von wem die nächste Antwort kommen muss. Anders ausgedrückt, du musst den Buszugriff sowieso serialisieren. Das kannst du * durch sorgfältig nacheinander erfolgende Aufrufen deiner Module machen. Dazu Nachverfolgen der Antworten und des Busstatus durch die Interrupts. * Du kannst auch auf die Interrupts verzichten (keine so gute Idee) und direkt nach dem Start einer Abfrage auf die Antwort warten. * Du kannst mit einem Puffer (FIFO), Code der den Puffer abarbeitet und einem Interrupthandler, der die Ergebnisse einreiht und den Busstatus verfolgt, arbeiten. Eher sinnvoll wenn du einen wilden Haufen von Slaves am Bus hast die durcheinander und zu vorher unbekannten Momenten abgefragt werden müssen. Ansonsten würde ich es lassen. Für extra Spaß - eher für wenn zusätzliche Sicherheit benötigt wird - kannst du das Eintreffen der Interrupts mit einem Timer absichern. Bleibt ein Interrupt innerhalb einer gewissen Zeit aus stimmt irgendwas ganz und gar nicht. Dann einen Fehlercode irgendwo eintragen wo ein µC-Reset überlebt wird und den Bus zurücksetzen. Das Bus-Zurücksetzen muss man meist händisch Programmieren. Oder den µC zurücksetzen, anhalten oder was auch immer sinnvoll ist. > Ich für den Sensor quasi nur noch das passende Header > File einbinden muss und dann alles ohne weitere Logik im Hauptprogramm > funktioniert. Unmöglich. Etwas in deinem Code, zum Beispiel das Hauptprogramm, muss die Sensordaten anwendungsspezifisch interpretieren und entsprechende Aktionen (eventuell auch wieder über I2C) ausführen.
Adam P. schrieb: > Ich habe mir dazu einen Job-Manager geschrieben. > Dieser nimmt Jobs entgegen, reiht diese ein und arbeitet diese ab. > Natürlich muss jedes Modul das einen Job übergibt auch eine Callback > Funktion mitliefern. Ja, dafür bekommt man gleich mal einen Minuspunkt! Hier sind wirklich Idioten unterwegs. Ich weiß natürlich nicht, wie der Jobmanager aussieht, aber das ist jedenfalls ein guter Ansatz. Wie ich das machen würde:
1 | Modul 1 behandelt Sensor A |
2 | Modul 2 behandelt Sensor B |
3 | Modul 3 behandelt die Schnittstelle. |
Beim Aufruf von Modul 3 übergibt das aufrufende Modul (1 oder 2) wer der Aufrufer war. Das kann ein function-pointer sein. Oder etwas primitiver eine Zahl mit deren Hilfe dann eine Funktion in Modul 1 oder 2 aufgerufen wird. Die Lösung ist aber wirklich schlampig, weil dann Modul 3 internas von Modul 1 und 2 wissen muss. Modul 1 & 2 werden wohl sehr kurz und behandeln nur irgendwelche Skalierungen. Sauber ist es, in Modul 3 ein typedef für die zu übergebende callback-function zu definieren. Die kennen somit Modul 1 und 2 (#include "modul3.h") und müssen sich dran halten. Modul 3 muss nichts von den aufrufenden Modulen wissen und kann beliebig oft von Anderen benützt werden. Vorsicht! Die callbacks NICHT innerhalb des INTs aufrufen. Modul 3 muss den INT komplett kapseln und bis zu Ende behandeln. Andernfalls handelt man sich ein, dass man den fehlerträchtigen code zwei mal implementieren muss. Man kommt also, ums einfach zu halten, nicht um ein polling durch Mod 1 & 2 rum. Kapselung bedeutet auch, dass man den Anwender des Moduls vor allen Fallen schützt.
Nick schrieb: > Beim Aufruf von Modul 3 übergibt das aufrufende Modul (1 oder 2) wer der > Aufrufer war. Das kann ein function-pointer sein Nick schrieb: > Sauber ist es, in Modul 3 ein typedef für die zu übergebende > callback-function zu definieren Die STM32CubeHAL übernimmt schon ganz wunderbar die Funktion von Modul 3, und implementiert das schon genau so mit Funktionszeiger+Callback. Nick schrieb: > Die callbacks NICHT innerhalb des INTs aufrufen. Modul 3 muss den INT > komplett kapseln und bis zu Ende behandeln. Andernfalls handelt man sich > ein, dass man den fehlerträchtigen code zwei mal implementieren muss Das musst du mal genauer erläutern. Nick schrieb: > Kapselung bedeutet auch, dass man den Anwender des Moduls vor allen > Fallen schützt. Das ist kaum machbar. Man kann auch Punkte offen lassen und die "Fallen" dokumentieren und manche Verantwortung dem Aufrufer überlassen.
Niklas G. schrieb: > Nick schrieb: >> Die callbacks NICHT innerhalb des INTs aufrufen. Modul 3 muss den INT >> komplett kapseln und bis zu Ende behandeln. Andernfalls handelt man sich >> ein, dass man den fehlerträchtigen code zwei mal implementieren muss > > Das musst du mal genauer erläutern. Gerne. Wenn man innerhalb eines INT eine callback-function aufruft, dann muss man sich klar darüber sein, dass diese function INT-sicher sein muss. Wenn also beispielsweise in der cbf ein uint32_t geändert wird und das nicht atomar ist (weil 16-Bit µC), dann KANN das schon unerwünschte Nebeneffekte haben. Noch schlimmer, wenn die cbf komplexeren code ausführt wie z.B. Displayroutine, komplexere Berechnungen die Daten rumschaufeln, etc. Man muss streng sicherstellen, dass die cbf bei nichts irgendwie bei irgendwas dazwischenfunkt. Und das geht nur, wenn die cbf erst nach Behandlung des INTs aufgerufen wird. Ja, das ist sehr dogmatisch, vermeidet aber seltsame Fehler wenn das Programm mal etwas komplexer wird. Bei einfachen Progrämmchen die nur mal schnell irnkwie was machen sollen kann man das ignorieren. Man sollte sich das aber garnicht erst angewöhnen. > Nick schrieb: >> Kapselung bedeutet auch, dass man den Anwender des Moduls vor allen >> Fallen schützt. > > Das ist kaum machbar. Man kann auch Punkte offen lassen und die "Fallen" > dokumentieren und manche Verantwortung dem Aufrufer überlassen. Kann man machen. Aus Anwendersicht führt das aber zu kopierten code, kopierten code-Mustern. Immer ein Indiz für schlampigen code. Man kann solche Ausnahmen im implementierenden code (hier Modul 3) komplett kapseln und die Aufrufer müssen keine Klimmzüge machen, damit Modul 3 richtig und wie erwartet arbeitet. Man steckt einmal Hirnschmalz in Modul 3 statt zig-mal in Modul X um die Unzulänglichkeit von Modul 3 auszubügeln. Ich schreib mein Zeug so, dass es wiederverwendbar ist, ohne Herrschaftswissen sondern mit einfachen, sicheren, klar definierten Schnittstellen. Ohne Fallen. Modul, modular, ein in sich geschlossenes System. Das sollte das oberste Ziel sein. Auch wenns manchmal nicht ohne möglichst einfache Konventionen geht wie "Aufrufer muss den übergebenen pointer deallokieren". Das ist ja auch die Kernfrage des TO. Sehr lobenswert sich da Gedanken drüber zu machen! Dafür geb ich ihm noch nachträglich ein "+". :-)
Nick schrieb: > dann muss man sich klar darüber sein, dass diese function INT-sicher > sein muss Das kann man ja beim Registrieren des Callbacks dokumentieren. Nick schrieb: > weil 16-Bit µC), Die STM32 sind keine 16bit-uC. Nick schrieb: > Und das geht nur, wenn die cbf erst nach Behandlung des INTs aufgerufen > wird. Man kann auch - Während der Bearbeitung kritischer Dinge die Interrupts sperren - Andere kritische Dinge ebenfalls in Interrupts laufen lassen mit gleicher oder höherer Priorität - Den Callback im Interrupt-Kontext aufrufen und im konkreten Callback erst bei Bedarf (!) einen "Task" in eine Warteschlange zur späteren Bearbeitung einreihen. Das ist auch im Linux-Kernel verbreitet und mit gängigen RTOSen auch möglich. Nick schrieb: > das ist sehr dogmatisch In der Tat. Nick schrieb: > Bei einfachen Progrämmchen die nur mal schnell irnkwie was machen sollen > kann man das ignorieren Zählt der Linux-Kernel als "einfaches Progrämmchen" für dich? Nick schrieb: > Ich schreib mein Zeug so, dass es wiederverwendbar ist, ohne > Herrschaftswissen sondern mit einfachen, sicheren, klar definierten > Schnittstellen Wie stellst du sicher dass die übergebenen Pointer garantiert gültig sind und nicht irgendwo ins Nirvana zeigen (nicht alles das ungleich 0 ist ist gültig)? Wie stellst du sicher dass man deine Funktion nicht aufrufen kann wenn der Stack voll ist? Wie stellst du sicher dass die übergebenen I2C-Adresse garantiert korrekt ist? Wie stellt Modul 3 sicher dass der Callback so schnell wie möglich aufgerufen werden kann, und auch andere, unwichtige Dinge unterbrechen kann? Das kann für Echtzeit-Systeme wichtig sein. Nick schrieb: > , damit Modul 3 richtig und wie erwartet arbeitet. Was wenn ich erwarte dass mein Callback im Interrupt-Kontext aufgerufen wird, damit ich flexibel entscheiden kann ob ich sofort reagiere oder es auch später in einem "pending Task" passieren kann? Nick schrieb: > Modul, modular, ein in sich geschlossenes System Das ist aber eben nicht modular. Wenn dein Modul 3 mit einem Task-System verdoppelt ist, kann man es nicht getrennt nutzen. I2C-Callbacks und Task Management sollten orthogonal sein. Der Aufrufer entscheidet ob er es zusammen verwenden möchte oder nicht.
Niklas G. schrieb: > Nick schrieb: >> weil 16-Bit µC), > > Die STM32 sind keine 16bit-uC. Das war ein Beispiel. Dann halt 64 Bit. Oder eine struct. > Nick schrieb: >> Und das geht nur, wenn die cbf erst nach Behandlung des INTs aufgerufen >> wird. > > Man kann auch > - Während der Bearbeitung kritischer Dinge die Interrupts sperren Ja, damit verlagert man die Verantwortung in beliebig viele andere Teile. Oder pflastert alles mit locks und semaphoren zu. Ich seh das äusserst kritisch. > - Den Callback im Interrupt-Kontext aufrufen und im konkreten Callback > erst bei Bedarf (!) einen "Task" in eine Warteschlange zur späteren > Bearbeitung einreihen. Das ist auch im Linux-Kernel verbreitet und mit > gängigen RTOSen auch möglich. Ich mach das auch so. Da brauchts auch kein RTOS dazu. Bei mir kommunizieren die Tasks über messages (und msg-queues). Den Aufwand wollte ich aber nicht vorschlagen. > Nick schrieb: >> das ist sehr dogmatisch > > In der Tat. Sag ich ja. Macht man auch, wie du sagst, genauso dogmatisch im Linux-kernel. > Nick schrieb: >> Bei einfachen Progrämmchen die nur mal schnell irnkwie was machen sollen >> kann man das ignorieren > > Zählt der Linux-Kernel als "einfaches Progrämmchen" für dich? Nein. Willst du damit sagen, dass im Linux-Kernel eine sichere INT-Behandlung für alle möglichen Fälle quer durch den code verteilt ist? Eher nicht (ich kenn den nicht), aber Deine obige Aussage sagt "Nein". > Wie stellst du sicher dass die übergebenen Pointer garantiert gültig > sind und nicht irgendwo ins Nirvana zeigen (nicht alles das ungleich 0 > ist ist gültig)? rust kann das. :-) Dass ein pointer nicht ins Nirvana zeigt ist Verantwortung des Moduls. Dass pointer-increments nicht ins nirvana zeigen ist Verantwortung des Anwenders oder der Programmiersprache (slices in rust). Deallokierte pointer werden natürlich immer auf NULL gesetzt, auch wenn es "unnötig" ist. Code kann sich nämlich verändern, Fallen bleiben bestehen. Und C bietet extrem viele Fallen an. Die muss man dann mit dogmatischen Vorgehen vermeiden. Ich kanns nicht grundlegend ändern. Wenn ich den code eines "Kollegen" anschaue (Java-Programmierer) der sich mal in C versucht hat und die Anwendung zuverlässig nach spätestens zwei Tagen abgestürzt ist, bestärkt mich das nur dogmatisch zu sein. Oder der (bewusst dumme) Spruch eines Kollegen wenn das Programm kompilierte: "Formal ist es schon mal richtig". Ja, ich verwende auch lint, selbst wenn er manchmal wirklich nervt. > Wie stellst du sicher dass man deine Funktion nicht > aufrufen kann wenn der Stack voll ist? Irgend einen Tod muss man sterben. Aber dafür gibt es auch tools die den Stackbedarf ermitteln. > Wie stellst du sicher dass die > übergebenen I2C-Adresse garantiert korrekt ist? Fängt das Modul ab, das die Adresse verwendet. Defensive Programmierung. Z.B. asserts in der Test/Entwicklungsphase. > Wie stellt Modul 3 sicher dass der Callback so schnell wie möglich > aufgerufen werden kann, und auch andere, unwichtige Dinge unterbrechen > kann? Das kann für Echtzeit-Systeme wichtig sein. Das kann das Modul nicht sicherstellen. Das ist in der Verantwortung des Aufrufenden. Denn nur der weiß, wie eilig er es hat. Damit nichts rumliegt was noch nicht abgeschlossen ist, muss das Modul das handhaben. Entweder in einer queue oder die Annahme verweigern. Message-basiert ist das aber trivial zu lösen. > Was wenn ich erwarte dass mein Callback im Interrupt-Kontext aufgerufen > wird, damit ich flexibel entscheiden kann ob ich sofort reagiere oder > es auch später in einem "pending Task" passieren kann? Wenn Du das ausdrücklich so haben willst, dann musst Du eine zweite Funktion in dem Modul anbieten die genau das macht. Ich kanns nicht ändern, dass Du das so haben willst. Nur würde ich den einfacheren Weg primär umsetzen. > Das ist aber eben nicht modular. Wenn dein Modul 3 mit einem Task-System > verdoppelt ist, kann man es nicht getrennt nutzen. I2C-Callbacks und > Task Management sollten orthogonal sein. Der Aufrufer entscheidet ob er > es zusammen verwenden möchte oder nicht. Wenn man ein Taskmanagement hat (ich hab nichts dagegen!), dann nimmt man das natürlich her. Wenn man verbissen genug ist, dann verlagert man das Task-Management in einen wrapper um das "Modul 3". Da kann man sich aber gerne drüber streiten. Meine Antwort wäre sehr stimmungsabhängig. :-)
Samuel schrieb: > Das nicht wissen ist hier natürlich etwas konstruiert, bei diesem > einfachen Fall kann man das sicher lösen. Möchte aber das ganze > allgemein lösen, das ich in Zukunft belibige Sensoren immer gleich > einbinden kann. Ich für den Sensor quasi nur noch das passende Header > File einbinden muss und dann alles ohne weitere Logik im Hauptprogramm > funktioniert. Vielleicht schaust Du Dir mal NuttX an. https://nuttx.apache.org/ Das ist ein RTOS, aber während das FreeRTOS, das auch bei CubeMX dabei ist, nur den Multitasking-Kernel liefert, enthält NuttX das komplette Paket, inkl Treiberlayer, Netzwerk- und USB Protokolle etc etc. Also quasi ein Linux in klein. Da hast Du auf der unteren Schicht Deine Bus-Treiber für I2C, SPI, GPIOs, ..., darüber dann die Chiptreiber für ADCs, DAC, PWM, GPIO-Externer etc, darüber dann die generischen Klassentreiber für GPIO, ADC, etc, und darauf Deine Applikation, die im Idealfall nur mit den generischen Klassentreibern spricht. Ob dann ein GPIO direkt am Prozessor dran ist, oder an einem SPI-GPIO-Extender ist dann egal. Wenn Du ein fertiges generisches Framework suchst und das Rad nicht neu erfinden willst, wäre das einen Blick wert. Ein Alternativprodukt wäre Zephyr, das z.B. von Nordic verwendet wird. Während bei NuttX alles statisch gelinkt ist, arbeitet Zephyr mit Device Trees und dynamischem Linking, ist also noch etwas mehr high-level, aber durch auch etwas fetter. NuttX ist relativ schlank und läuft problemlos auf TI TM4C und STM32F4/F7/H7. fchk
Wieso driftet das jetzt in ein RTOS ab? Habe jetzt in der Ursprungsfrage nichts von RTOS oder Multitasking gelesen. Ich kenne nun die Anwendung nicht, aber braucht es heute für jede Kleinigkeit gleich ein RTOS? Samuel schrieb: > einbinden kann. Ich für den Sensor quasi nur noch das passende Header > File einbinden muss und dann alles ohne weitere Logik im Hauptprogramm > funktioniert. Irgendwie wirst Du die verschiedenen Sensoren ja eh auseinanderhalten müssen. Im einfachsten Fall durch eine Nummerierung bzw. aus der IIC-Adresse. Weitere Sensoren "automatisch" in der Anwendung zu "registrieren", indem man nur deren H-file benennt, ist schon etwas sportlicher. Kleine Nebenfrage: Wieso gibt es in Windows eigentlich ein Registry? Wenn Du z.B. spezielle Daten wie Kalibrierdaten oder Bereichsgrenzen oder Messwerteinheiten zusammen mit einem Funktionspointer für die Abarbeitungsroutine für jeden Sensor in eine Struktur packst und ein Array aus diesen Strukturen machst, kann beim Sensoraufruf mittels der Nummer bzw. Adresse auf den Strukturinhalt und den Funktionspointer zugegriffen werden. Für 2 Sensoren jetzt vielleicht unnötig, aber wenn später mehrere dazu kommen, ist es flexibel. Eventuell könnte Dein Hauptprogramm dieses Array dann aus den zur Verfügung stehenden Informationen der einzelnen h-files selbst zusammenbasteln. Ich habe das mit dem Array bei einer komplexen Menüstruktur mit vielen Untermenüs gemacht. Da "weiß" die Anwendung außer einer eindeutigen Nummer des ausgewählten Menüpunkts zunächst auch nicht, was sie nun damit anfangen soll. Es wird dem Hauptprogramm nur gemeldet dass ein Menüpunkt angewählt wurde. Das kann ja auch ein Interrupt sein, wie in meinem Fall ein Encoderimpuls. Da steht dann nur der aktuelle Zählerwert des Drehencoders. Das Abarbeiten wird erst beim Zugriff auf die jeweilige Struktur für diesen einen Menüpunkt aus dem Array über den Zählerwert (Adresse) möglich, wo dann auch die Funktionsaufrufe über die Pointer ausgelöst werden, die entweder gleich sein können oder für jeden Punkt spezifisch, oder auch "mache nichts". Der Vorteil ist, man kann für jeden Menüpunkt oder ein Deinem Fall für jeden Sensor noch spezifische Daten, z.B. Umrechnungsfaktoren o.ä. in die Struktur schreiben, die für jeden Sensor anders sein können. In meinem Fall liegen da noch Min- bzw. Maxwerte drin, Zählinkremente für den Drehencoder, das Anzeigeformat Float, Dez oder Hex sowie Zeiger auf das jeweils übergeordnete Menü. Im Sourcefile des jeweiligen Sensors würde die Auswertefunktion oder sonstige spezielle Funktionen für diesen einen Sensor stehen. Im H-File des Sensors können dann die Deklarationen für die Funktionspointer und die sensorspezifischen Eigenschaften (weitere Strukturinhalte) stehen. Irgendwo im Haupt- oder Initprogramm muss dann eben einmal das Array mit der Struktur für jeden Sensor in der korrekten Adressierung (Nummerierung) zusammengebaut werden. Kommt ein Sensor dazu, wird der an das Array angehängt, NUM_SENSORS um eins erhöht und fertig. Die Struktur muss eben für alle Sensoren passen. Voraussetzung ist natürlich dass jeweils nur 1 Sensor oder in meinem Fall ein Menüpunkt ausgewählt wird. Geht ja technisch auch nicht anders. Ich kann nicht 2 Menüpunkte gleichzeitig auswählen und auf dem IIC quatschen auch nicht mehrere Sensoren gleichzeitig. "Modularisieren" ist natürlich immer gut, aber man kann gerade bei Beginn einer Entwicklung nicht immer alle nötigen Eigenschaften der Schnittstelle zwischen den Modulen eindeutig vorausplanen. SOLLTE, IST aber meistens nicht ;-) Und übertreiben sollte man den Wunsch nach Modularisierung auch nicht, denn sonst kommt man irgendwann an den Punkt, sein eigenes OS entwickeln zu wollen. Ich habe bei meinen Projekten oft erst später Teile nachträglich in Module gepackt und deren Schnittstellen optimiert, nachdem ich gesehen habe, dass ich diese auch wirklich öfters benutze. Die Mehrzahl wird aber meistens nur einmal bzw. selten benötigt, so dass es sich nicht lohnt, alles für alle Zeiten immer wieder für alle Projekte auf allen Systemen einsetzbar zu gestalten. Ein Jahr später hat man meistens schon eine viel bessere Lösung für ein ähnliches Problem entwickelt und will das alte Zeug in der Art sowieso nicht mehr einsetzen. Und wenn man es will, gibt es garantiert irgendeine Kleinigkeit, warum das ach so perfekte Modul doch nicht passt und geändert bzw. eine Variante davon erstellt werden muss. Gruß Joachim
Niklas G. schrieb: > Bruno V. schrieb: >> Arbeitest Du mit parallelen Tasks, kann die Funktionen blockierend sein > > Soll sie ja nicht, er hat explizit nach Callbacks gefragt. Und genau > dieses Problem löst so ein Task Scheduler erstmal nicht. Wen die Sensoren alle ihre eigene Task haben, dienen die Callbacks nur dazu, die Funktion quasi-blockierend zu machen. * Start_Auftrag und warte auf Flag * Callback löscht das Flag * und weiter gehts Bei einer Task pro Sensor kann man das Datenblatt "so runterprogrammieren". Anfänger verwenden es daher gerne und fallen beim Callback auf RaceConditions rein. Blockierend ist da "idiotensicher". (ich bin von alledem aber kein Fan!)
:
Bearbeitet durch User
Man sollte sich zeitnah eine A380 zulegen, falls man mal wieder des Morgens Brötchen holen möchte.
Vielen Dank für die Diskussion. Gibt mir mal einige Gedankenanstösse. Habe momentan nur zwei Sensoren. Das könnte ich wohl belibig simpel lösen und es würde funktionieren. Habe mir mehr gedacht wenn ich das jetzt "sauber" mache, kann ich das in Zukunft einfach erweitern und auch für andere Projekte benützten. Ein RTOS wäre da für meine Anwendungsfall wohl etwas zu viel. Werde mal versuchen eine Version wie hier vorgeschlagen umzusetzen: Nick schrieb: > Wie ich das machen würde:Modul 1 behandelt Sensor A > Modul 2 behandelt Sensor B > Modul 3 behandelt die Schnittstelle.
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.