Mehrkernnutzung und/oder Multitasking

Aus Technische Beeinflussbarkeit der Geschmacksache Kaffee
Zur Navigation springen Zur Suche springen

Peter Vogginger, 15.01.2025
Versuch der Mehrkernnutzung

Die Mehrkernnutzung wird versuchshalber mit einem zusätzlichen Schrittmotor und der bereits programmierten UART-Kommunikation getestet. Die Schrittmotorsteuerung wird auf den Hauptkern (Core 0) und die Kommunikation auf den 2. Kern (Core 1) verlagert. Diese Aufteilung wird gewählt, da es vermutlich zu einem besserem Motorlauf führt. Dies wird im weiteren Verlauf auch versucht, zu bestätigen.

Der Versuchsaufbau besteht aus zwei RP2040-Platinen, dem Schrittmotor, sowie dessen Ansteuerung/ Stromversorgung (realisiert auf einer zusätzlichen Platine). Die RP2040-Platinen sind miteinander verkabelt (siehe hierzu Seite Kommunikation_per_UART). Die Platine mit der Schrittmotorsteuerung ist als Relais in dem Kommunikationsring ausgeführt. Die zweite RP2040 beinhaltet den Code für den ersten Token (Messwert-Platine) und startet die Kommunikation.

Die Versuchsdurchführung sieht vor, dass der Schrittmotor permanent drehen soll und bei Beginn der Kommunikation die Drehbewegung des Motors beobachtet wird. Der Programmcode des Motors wurde in Teilen aus einer früheren Projektarbeit entnommen, sowie aus dem offiziellen MicroPython-Wiki (siehe: https://docs.micropython.org/en/latest/library/rp2.html). Die Kommunikation über UART wird als reines Relais ausgeführt, das heißt der eingehende Token wird kontrolliert und weitergesendet. Dies wurde bereits erfolgreich angewendet.

Läuft sowohl die Kommunikation, ersichtlich über das Ausgabefenster in THONNY, und dreht der Motor ebenso, ist die Mehrkernnutzung erfolgreich getestet. Im weiteren Verlauf wird das Drehverhalten der Motorwelle mit und ohne parallele Kommunikation beurteilt und verfeinert. Im ersten Versuch zeigt sich, dass sich die Welle des Motors ohne Mehrkernnutzung nahezu ruckfrei dreht, allerdings in paralleler Ausführung sich ein starkes Rucken einstellt. Um dies zu reduzieren, wird die Motor-Ansteuerung auf die spezialisierte Hardware-Einheit PIO des RP2040 verlegt. Sie ermöglicht es, Ein- und Ausgabesignale sehr präzise und unabhängig von der CPU zu steuern, indem eigene kleine Programme (ASM-Code) direkt in der PIO-Hardware ausgeführt werden. Diese ist fest auf dem RP2040 integriert, es wird nur der Programmcode der Schrittmotorsteuerung angepasst.

Daraufhin zeigt sich, dass sich mit dieser Art der Motorsteuerung ein weniger ruckeliges Verhalten während der parallel ablaufenden Kommunikation einstellt. Allerdings ist die Drehung der Motorwelle, ohne die Verwendung der Mehrkernnutzung, weniger flüssig. Dies ist hier allerdings nicht entscheidend.

Ebenso zeigt sich, dass die Verlagerung der Kommunikation auf den Nebenkern nicht zu dem oben erwähnten besseren Motorlauf führt. Ganz im Gegenteil, eine Verlagerung der Motorsteuerung auf den Nebenkern (Core1), wobei zugleich die Kommunikation auf dem Hauptkern (Core0) ausgeführt wird, erweist sich als deutlich besser im Hinblick auf den sofortigen Kommunikationsaufbau, und die Motorbewegung ist weniger ruckelig.
Link zum bisherigen Programmcode: Datei:20260108 Test Dualcorenutzung.zip

Im weiteren Verlauf wird die Positionierung der Schrittmotorwelle mittels Datenwert im Token übermittelt und dessen korrekte Anfahrung getestet. Der Schrittmotor hat hierzu die Position 0.

Die Ausarbeitung des Programmcodes ist aufwendig und wird im Folgenden anhand der drei Ablaufdiagramme (Abb..) vorgestellt.

Ablaufdiagramm der Funktion schrittmotor_steuerung, (c) Peter Vogginger
Ablaufdiagramm der Funktion schrittmotor_steuerung, (c) Peter Vogginger
Ablaufdiagramme der Funktionn set_target_steps und motor_run, (c) Peter Vogginger
Ablaufdiagramme der Funktionen set_target_steps und motor_run, (c) Peter Vogginger

schrittmotor_steuerung

Die Funktion schrittmotor_steuerung ist Teil der main.py der SSR-Platine und wird bei jedem Aufruf der lokalen Funktion on_receive_ssr ausgeführt. Somit wird sie bei jedem Eintreffen eines neuen Tokens über die UART-Schnittstelle aufgerufen (http://www.institut-fuer-kaffeetechnologie.de/Wiki/index.php?title=Kommunikation_per_UART).

Die Aufgaben dieser Funktion sind:

• das Auslesen der Zielposition der Motorwelle aus dem Token,
• das Schreiben der aktuellen Istposition des Motors in den Token,
• sowie der Aufruf der Funktion set_target_steps, sofern erforderlich.

Die Schrittanzahl wird in Halbschritten angegeben und aus dem Token mithilfe eines Getters ausgelesen. Hierbei ist zu beachten, dass es sich um einen Integerwert mit Vorzeichen handelt und somit der Setter entsprechend set_signed_bytes() heißt (http://www.institut-fuer-kaffeetechnologie.de/Wiki/index.php?title=Kommunikation_per_UART Setter und Getter für Bits und Bytes).

Die Istposition ist die absolute Schrittanzahl vom Zustand 0 (beim Dosierventil ist das der Zustand „geschlossen“). Die Istposition wird bei jedem Tokenempfang mithilfe eines Setters in den Token geschrieben und somit fortlaufend aktualisiert. Der Wertebereich des Dosierventils ist 0…550. Somit wird der Setter set_bytes() verwendet, da keine negativen Werte vorkommen können.

Zusätzlich überprüft die Funktion, ob der Motor im Moment dreht. Hierzu dient das Flag motor_dreht. Dieses Flag wird in den Funktionen set_target_steps und motor_run gesetzt bzw. zurückgesetzt. Dreht der Motor aktuell, wird kein neuer Positionierbefehl ausgelöst, um den Bewegungsablauf nicht zu stören. Nur wenn der Motor steht und die Zielposition von der Istposition abweicht, wird die Funktion set_target_steps erneut aufgerufen.

set_target_steps

Die Funktion set_target_steps ist Teil der Klasse MOTORSTEUERUNG in der Datei motorsteuerung.py auf der SSR-Platine. Die Aufgaben dieser Funktion sind:

• der Vergleich der vorgegebenen Schrittanzahl mit der Minimal- und der Maximalposition
• die Festlegung der Drehrichtung des Motors, sowie
• das Setzen und Zurücksetzen des Status-Flags motor_dreht.

Im ersten Schritt wird überprüft, ob die vorgegebene Schrittanzahl target_steps kleiner als die minimal mögliche Position (0) ist. Falls dies zutrifft, wird die Schrittanzahl auf die Minimalposition 0 gesetzt. Anschließend wird geprüft, ob die Schrittanzahl größer als die Maximalposition des Dosierventils (550) ist. Ist dies der Fall, wird die Schrittanzahl auf die Maximalposition gesetzt. Die Zielposition ist nun die Schrittanzahl.

Darauffolgend wird die Zielposition mit der aktuellen Istposition verglichen, um die Drehrichtung des Motors festzulegen. Ist die Zielposition größer als die Istposition, wird der Motor in positiver Drehrichtung (= rechtsdrehend bzw. schließend) betreiben, ist sie kleiner, erfolgt die Bewegung in negativer Drehrichtung (= linksdrehend bzw. öffnend

Abschließend wird das Flag motor_dreht gesetzt. Dieses dient dazu, dass während einer laufenden Motorbewegung die Funktion set_target_steps nicht erneut aufgerufen wird. Dadurch wird eine Störung der Motorbewegung und des Ablaufs verhindert. Die Funktion motor_steuerung wird weiterhin bei jedem Eintreffen eines neuen Tokens aufgerufen.

motor_run

Die Funktion motor_run ist Teil der Klasse MOTORSTEUERUNG in der Datei motorsteuerung.py auf der SSR-Platine.

Die Aufgabe dieser Funktion ist die kontinuierliche Ansteuerung des Schrittmotors auf dem zweiten Prozessorkern (Core 1).

Die Funktion wird mit dem Aufruf _thread.start_new_thread(motor.motor_run,()) gestartet und läuft über eine Endlosschleife während der gesamten Einschaltdauer der Platine. Diese Struktur ist notwendig, um die Mehrkernnutzung des RP2040 zu realisieren und eine gleichzeitige Motorbewegung sicherzustellen.

Zu Beginn wird der Index idx der Schrittsequenz auf null gesetzt. Dieser dient als Laufvariable zur Auswahl des aktuellen Spulenzustandes.

Anschließend wird innerhalb einer Schleife permanent überprüft, ob das Flag motor_dreht gesetzt ist. Dieses Flag wird in der Funktion set_target_steps aktiviert, sobald eine neue Zielposition angefordert wurde. Ist das Flag gesetzt, wird ein vorgegebenes Schrittmuster ausgegeben. Das Schrittmuster ist motorabhängig und besteht bei dem verwendeten bipolaren zweiphasigen Schrittmotor aus acht verschiedenen Spulenzuständen: STEP_SEQ = (0b0100, 0b0101, 0b0001, 0b1001, 0b1000, 0b1010, 0b0010, 0b0110). Die Gesamtheit der Einträge ist die Schrittfrequenz. Hierbei handelt es sich um die Halbschritt-Sequenz, welche für das Dosierventil vorgesehen ist.

Die Ausgabe des aktuellen Spulenzustands erfolgt über den Aufruf self.sm.put(self.STEP_SEQ[idx]). Jeder Eintrag der Schrittfrequenz entspricht genau einem Halbschritt.

Der Schrittindex wird anschließend durch den Aufruf idx = (idx + self.drehrichtung) % 8 aktualisiert. Abhängig von der Drehrichtung ergibt sich dabei folgender Zusammenhang:

• Ist die Drehrichtung +1, lautet die Abfolge 0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 0 → …
• ist die Drehrichtung -1, lautet die Abfolge 0 → 7 → 6 → 5 → 4 → 3 → 2 → 1 → 0 → …

Durch den Modulo-Operator wird sichergestellt, dass der Index stets im gültigen Bereich der Schrittfrequenz bleibt. Bei jedem Schleifendurchlauf wird die Variable idx genau einmal verändert, somit entspricht jeder Schleifendurchlauf exakt einem Motor-Halbschritt.

Die aktuelle Position wird anschließend mit self.aktuelle_position += self.drehrichtung aktualisiert.

Ob die Zielposition erreicht wurde, wird durch den Vergleich self.aktuelle_position == self.ziel_position überprüft. Ist dies der Fall, wird das Flag motor_dreht auf False gesetzt, wodurch die Motorbewegung beendet wird. Die Schleife läuft weiterhin.

Der Zugriff auf das Flag motor_dreht wird in der Funktion motor_run benötigt, da es die Motorverstellung aus der Bewegung heraus stoppt. In der Funktion set_target_steps wird dagegen entschieden, ob der Motor überhaupt drehen soll und das Flag wird entspechend gesetzt.

Armin Rohnen, 11.04.2024

Grundsätzlich verfügt der ARM-Prozessor der Raspberry Pi Pcio MCU RP2040 über zwei Kerne, die im Gegensatz zu anderen MCUs gleichwertig sein sollen. Die Quellenlage hierzu lässt jedoch eine eindeutige Beurteilung dieser Annahme nicht zu. Eine ggf. verbessernde Quellenlage könnte hierzu neue Ansätze liefern. Für die Verlagerung der Regelkreise auf die MCUs ist zuvor eine Festlegung zu treffen in welcher Form eine Mehrkernnutzung und/oder ein Multitasking implementiert wird.

Damit ist ein Framework für die Systemsteuerung zu definieren. Hierzu sind in der Linksammlung [119] unter Scheduling, Threading und Frameworks einige Libraries aufgelistet.

MicroPython stellt hierzu die Module uasyncio und _thread zur Verfügung. Letzteres verwendet für die Programmausführung den zweiten Prozessorkern. _thread wird allerdings als sehr experimentell bezeichnet. Allerdings lassen sich keine Negativinformation recherchieren.