Erste Schritte mit RT-Linux: Ansteuerung eines Schrittmotors

  RTLinux   Installation   Entwicklung unter RTLinux   Ansteuerung eines Schrittmotors   Programm   Bibliographie
Manche Anwendungen sind so komplex, dass die Entwicklung auf einem Microcontroller zu zeitaufwendig wird. Dann muss man oft wohl oder übel zu einem PC greifen, um rasch zu brauchbaren Ergebnissen zu gelangen. Doch wie bekommt man Echtzeit - Anwendungen auf einen PC, ohne auf den Komfort eines modernen Betriebssystems verzichten zu müssen? Diese Seite untersucht am Beispiel einer Schrittmotor - Steuerung, inwieweit RTLinux dafür geeignet ist, und ob es sich auch von 'Normalsterblichen' programmieren und einsetzen lässt.

Warum RTLinux

Nach einigen durchaus erfolgreichen Versuchen, meine Fräsanlage mit einem Microcontroller zu steuern, bleibt vor allem eines festzuhalten: es ist ohne weiteres möglich, drei Schrittmotore mit einem uC synchron zu steuern. Jedoch ist die Fehlersuche äusserst schwierig, und man programmiert ewig daran, das notwendige Drumherum aus Datenspeicher, manuelle und automatische Steuerung und Synchronisation der verschiedenen Controller zu realisieren.

Da man zur Datenspeicherung -- dafür ein Flash - ROM zu verwenden, ist auf Dauer unpraktisch -- ohnehin einen Rechner neben der Anlage braucht, kann dieser auch die komplette Schrittmotor - Steuerung übernehmen. Ganz so einfach ist das dann aber doch nicht: schließlich ist die Erzeugung von Schrittmotor - Takten eine 'harte' Echtzeitanwendung. Sind die generierten Takte nicht synchron, so ist an eine präzise Arbeit nicht mehr zu denken.

Die Echtzeit - Fähigkeit hat übrigends nicht das geringste mit der Ausführungsgeschwindigkeit zu tun. Es geht keinesfalls um ein so schnell wie möglich, wie es die als "Realtime" bezeichneten Prioritäten diverser Multitasking - Betriebssysteme nahelegen. Vielmehr liegt das Ziel eines echtzeitfähigen Betriebssystems darin, eine Aufgabe genau rechtzeitig auszuführen, nicht früher und nicht später. Nicht echtzeitfähige Betriebssysteme können diese Rechtzeitigkeit nicht garantieren.

Übliche kommerzielle Programme zur Steuerung von Fräsanlagen verwenden DOS als Betriebssystem, das zwar ebensowenig echtzeitfähig ist wie Linux oder Windows, jedoch als Singletasking - Betriebssystem der Anwendung die volle Kontrolle über die Maschine gibt. Jedoch ist DOS reichlich angestaubt, und um darauf eine Echtzeit - fähige Steuerung zu realisieren, muss man tief in das Betriebssystem eingreifen und nahezu alle benötigten Funktionen und Programme selbst entwickeln.

An dieser Stelle kommt RTLinux in's Spiel: es ist eine Erweiterung für den Standard - Linuxkernel, die sich vollkommen transparent in's System integriert. Alle Linux - Anwendungen laufen weiterhin, ob es nun eine Oracle - Datenbank oder der Lieblings - Texteditor ist. Linux ist ein modernes Multitasking - Betriebssystem mit ausgezeichneten Netzwerkfähigkeiten und einer Unmenge an frei verfügbaren Entwicklungswerkzeugen und Programmbibliotheken.

Inwieweit ist nun RTLinux in der Lage, zur Steuerung von Schrittmotoren Verwendung zu finden? Und vor allem, ist es auch von einem Nicht - Guru und Nicht - Kernelhacker (wie mir ;-) sinnvoll einzusetzen? Diesen Fragen will diese Seite auf den Grund gehen. Dazu soll die Installation von RTLinux 3.1 auf einem SuSE 7.2 System mit Kernel 2.4.4 durchgeführt und ein einfaches Programm zur Steuerung eines Schrittmotors über den Parallelport entwickelt werden.
zurück zum Anfang

Die Installation

Grundsätzlich sollte man sich für Experimente mit RTLinux einen Zweitrechner aufstellen. RTLinux - Threads laufen im Kernelspace, sodass der Rechner bei Fehlern im Programm öfters neu gebootet werden muss; währenddessen kann man auf seinem Arbeitsrechner weitermachen. Auf Monitor, Tastatur und Maus für den Zweitrechner kann verzichtet werden, da man per Displayumleitung auch sehr gut über's Netz arbeiten kann.

Zunächst ist Linux auf dem Rechner - bei mir ein alter Pentium 133 mit 80 MB RAM, 1.2 GB Festplatte und 100MBit Netzwerkkarte - zu installieren. Bei den begrenzten Ressourcen auf dem Rechner ist es sinnvoll, bei der verwendeten SuSE 7.2 Distribution die Option Minimalsystem mit grafischer X11 - Oberfläche zu wählen, um nicht zuviel Plattenplatz für unnötige Programme zu verschwenden. Bei diesem Minimalsystem muss man hinterher allerdings noch die Netzwerk - Serverkomponenten (ftp-, telnet- rlogin - Deamon, xinetd etc.) hinzuinstallieren.

Nun kann man das System konfigurieren, wie man es für richtig hält. Vor allem sind die Netzwerkdienste einzurichten, da man danach auf Monitor, Maus und Tastatur verzichten und den Rechner in einem Winkel unter dem Schreibtisch verstauen kann. Trägt der installierte Kernel nicht die Versionsnummer 2.4.4 (cat /proc/version), so ist er zu installieren.

Jetzt geht es an die Installation von RTLinux. Zuerst sind die Quellen von RTLinux 3.1 sowie die 2.4.4er Kernel - Quellen herunterzuladen. Da RTLinux als Kernel - Patch ausgeliefert wird, ist unbedingt ein originaler, ungepatchter Kernel von www.kernel.org erforderlich, die Quellen des SuSE - Standardkernels patchen zu wollen ist aussichtslos.

Nun werden die Quellen entpackt:
  cd /usr/src/
  tar xvzf linux-2.4.4.tar.gz
  tar xvzf rtlinux-3.1.tar.gz
Gemäß den Linux - Konventionen ist noch ein Link von den aktuellen Kernel - Sourcen nach linux anzulegen:
  ln -sf /usr/src/linux-2.4.4 /usr/src/linux

Daraufhin werden die Kernel - Sourcen mit der RTLinux - Erweiterung gepatcht.
  cd /usr/src/linux
  patch -p1 < ../rtlinux-3.1/kernel_patch-2.4.4

Die Kernel - Konfiguration ist der nächste Schritt. Nach 4 gescheiterten Versuchen weiß ich es genau: für einen 'Uneingeweihten' ist es nahezu unmöglich, einen lauffähigen Kernel selbst zusammenzustellen. Irgendetwas vergisst man immer, sei es eine wichtige Komponente zum Ansprechen der Festplatte oder ein Modul mit unwichtig klingendem Namen, das so unwichtig dann doch nicht sein konnte. Es gibt aber einen guten Trick, dennoch zu einem funktionierenden Kernel zu gelangen. Bei der Einrichtung wurde darauf hingewiesen, den 2.4.4er Kernel von SuSE zu installieren. Dieser ist zwar von SuSE angepasst, aber die Konfigurationsdatei scheint zum Originalkernel kompatibel zu sein. Diese ist also als Vorlage zu kopieren:
  cp /boot/vmlinuz.config /usr/src/linux/.config

Mit dieser Vorlage braucht man nur diejenigen Module abzuwählen, von denen man mit Sicherheit ausschließen kann, dass man sie jemals braucht. Dazu zählen Sound, Video, USB, SCSI, zahllose Netzwerkkarten - Treiber etc. Auszuschalten sind auch ACPI und der Parallelport - Support. Es ist auch ganz praktisch, sämtliche wirklich benötigten Module direkt in den Kernel unterzubringen, weil dann ein Aufruf von lsmod nur noch die RTLinux - Module anzeigt.
  cd /usr/src/linux
  make xconfig

Nun können der Kernel und die ausgewählten Module übersetzt werden. Das dauert ein Weilchen, wenn man bei der Abwahl unnötiger Module zu zaghaft war.
  make bzImage
  make modules
  make modules_install

Als Ergebnis liegt nun ein Kernel - Image in arch/i386/boot/bzImage und ein neues Module - Verzeichnis in /lib/modules/2.4.4-rtl/. Um vom neuen Kernel zu booten, ist dieser an die richtige Position zu kopieren.
  cp arch/i386/boot/bzImage /boot/rtzImage

Weiterhin ist ein Eintrag beim Bootloader -- hier am Beispiel von lilo -- zu ergänzen. In die Datei /etc/lilo.conf gehören diese Zeilen unter Anpassung des Pfades zur Root - Partition:

image  = /boot/rtzImage
label  = rtlinux
root   = /dev/hda3

Damit lilo diese Änderungen an seiner Konfigurationsdatei auch wahrnimmt, ist er einmal aufzurufen. Daraufhin kann gebootet werden. Hat alles funktioniert, steht nun ein neuer Eintrag rtlinux im Bootmenü, der den neu erstellten Kernel startet.
  lilo
  shutdown -r now

Wenn das System erfolgreich hochgefahren ist, kann man sich leicht davon überzeugen, dass der soeben kompilierte Kernel mit RTLinux - Erweiterungen läuft. Ein Aufruf von cat /proc/version sollte nun den Text "Linux version 2.4.4-rtl" ausgeben.

Es bleibt noch das Kompilieren der RTLinux - eigenen Module, die nicht in die Sourcen des Linux - Kernels hineinkopiert wurden. Ein Link von den Linux - Kernensourcen in das RTLinux - Verzeichnis dient dabei zum Einbinden der Kernelquellen. Bei der Konfiguration von RTLinux mit make xconfig sind die Vorgaben sinnvoll, es genügt ein Mausclick auf "Save and Exit".
  ln -sf /usr/src/linux-2.4.4/ /usr/src/rtlinux-3.1/linux
  cd /usr/src/rtlinux-3.1
  make xconfig
  make
  make devices

Die Installation ist nun erfolgreich abgeschlossen. Mit diesen Kommandos (die man sich am besten gleich in ein Makefile oder ein Shellscript packt) werden die RTLinux - Module geladen, wovon man sich wieder mit lsmod überzeugen kann:
  cd /usr/src/rtlinux-3.1/
  scripts/insrtl

Alle Module stehen jetzt fertig kompiliert zur Verfügung, und die in /usr/src/rtlinux-3.1/examples stehenden Beispiele sollten zur Zufriedenheit funktionieren. Letztlich sollte jeder, der auch Linux selbst installiert bekommt, mit RTLinux keine Schwierigkeiten haben.
zurück zum Anfang

Entwicklung unter RTLinux

RTLinux - Threads laufen grundsätzlich als Kernel - Modul (die neue Echtzeit - Userspace Erweiterung habe ich noch nicht getestet) ab. Sie werden also als .o-File kompiliert, mit insmod modul.o gestartet und rmmod modul gestoppt. Sie beginnen darum auch nicht wie normale Applikationen mit einer main() - Funktion, sondern werden über spezielle Funktionen initialisiert und beendet:

  int init_module(void)
  {
      ...
      return 0;
  }
 
  void cleanup_module(void)
  {
      ...
  }

Meldungen werden nicht auf die Konsole ausgegeben -- wie auch, welche Konsole gehört dem Kernel? -- sondern in den Kernel Ring Buffer geschrieben, wo sie sich mit dmesg anzeigen lassen. Programmseitig steht für die Ausgabe solcher Nachrichten die Funktion rtl_printf() zur Verfügung, die ebenso wie das bekannte printf() parametrisiert wird.

Da diese Kernel - Module mit Root - Rechten ablaufen, können sie auf alle Hardware direkt zugreifen, sind aber auch anfällig für Programmierfehler oder gar Angriffe von aussen. Die übliche Reaktion von RTLinux auf Fehler gleich welcher Art besteht darin, das System sofort anzuhalten. Sollte also Ihr System aus unerfindlichen Gründen stehenbleiben, so ist dies nicht auf Fehler im Linux - Kernel zurückzuführen, sondern Absicht.

Im Unterschied zum normalen Linux gehört zu den Fehlern, die zum Einfrieren des Kernels führen, auch die Überlastung. Wenn Echtzeit - Threads nicht im Rahmen der angegebenen Zeiten ausgeführt werden können, können durchaus gefährliche Situationen entstehen, beispielsweise wenn bei einer 3 Achsen - Fräsanlage nur für die Bewegung von 2 Achsen genug Rechenleistung bereitsteht und die dritte nicht hinterherkommt. Das Anhalten des Systems ist dabei eine sinnvolle Vorgehensweise zum Verhindern größerer Schäden.

Dies ist sicher sinnvoll für Produktivumgebungen, da es besser ist, in einem definierten Zustand zu enden als in einem unvorhersehbaren Chaos aus laufenden und abgeschossenen Modulen und verspäteten Threads. Für die Entwicklung ist dieses Verhalten hingegen lästig und lässt sich über das Laden des debug - Moduls mittels insmod /usr/src/rtlinux-3.1/debugger/rtl_debug.o abstellen (Siehe README im debugger - Verzeichnis von RTlinux).

Die Echtzeitfähigkeit der Threads muss man auch bei der Ein- und Ausgabe berücksichtigen. So ist es zum Beispiel ausgeschlossen, in Echtzeit - Threads Dateien zu lesen oder Netzwerkverbindungen zu öffnen, da deren Ein- und Ausgabefunktionen den Prozess unvorhersagbar lange blockieren. Die einfachste Art, Daten in einen Echtzeit - Thread zu bekommen oder von ihm auszulesen, besteht in den FIFOs (First In First Out), die bei der Installation als /dev/rtf0 ... /dev/rtfn angelegt wurden. Sie lassen sich von nicht echtzeitfähigen Programmen und Prozessen wie normale Dateien bzw. Geräte ansprechen und von den Echtzeit - Prozessen aus mit rtf_get() und rtf_put() leicht lesen und schreiben.

Inzwischen wurde bereits oft von Echtzeit - Prozessen gesprochen -- doch wie werden diese erzeugt und verwaltet? Ein periodischer RT - Thread, wie er für die Erzeugung von Schrittmotor - Steuersignalen benötigt wird, wird mit pthread_create() erzeugt, mit pthread_make_periodic_np() als periodisch markiert und mit pthread_delete_np() vernichtet. Hat der Thread seine Aufgabe für die jeweilige Periode erfüllt und wartet auf die nächste, so legt er sich mit pthread_wait_np() schlafen.

Ein einfaches Beispiel, dessen Ausgabe sich wie oben erwähnt mit dmesg ansehen lässt, und das wie ebenfalls erläutert mit insmod test.o und rmmod test gestartet und gestoppt wird, sieht so aus:

  pthread_t t;
 
  void* rt_thread(void* arg)
  {
      pthread_make_periodic_np (pthread_self(), gethrtime(), 5000000);
 
      while(1)
      {
          rtl_printf("I am alive!\n");
          pthread_wait_np();
      }
  }
      
  int init_module(void)
  {
      rtl_printf("Start.\n");
      pthread_create(&t, NULL, rt_thread, (void*) 0);
      return 0;
  }        
 
  void cleanup_module(void)
  {
      rtl_printf("Stop.\n");
      pthread_delete_np(t);
  }

zurück zum Anfang

Ansteuerung eines Schrittmotors

Bipolare Schrittmotore sind sehr grob vereinfacht gesehen 'herumgedrehte' Gleichstrommotore: die Ankerwicklungen bilden den Stator, und ein Permanentmagnet den Rotor. Da der Schrittmotor somit ohne Kommutator (das Element am Elektromotor, an dem die Kohleschleifer sitzen, und das die Stromrichtung am Anker umschaltet) auskommen muss, ist eine Elektronik oder Programmlogik dafür zuständig, diese Umschaltung vorzunehmen.

Solange ein Muster an den Spulen anliegt, verharrt der Läufer in der aktuellen Position. Daher lässt sich die Drehzahl und die Position eines Schrittmotors sehr exakt bestimmen.

Im einfachsten Falle erfolgt die Ansteuerung mit dem Vollschrittverfahren. Dabei wird die Spannung von Spule zu Spule mit wechselseitiger Polarität weitergeschaltet, wie unten abgebildet.

Das ebenfalls abgebildete Halbschrittverfahren fügt zwischen die beiden Vollschritte noch einen weiteren Schritt ein, in dem beide Spulen stromdurchflossen sind. Dadurch lässt sich die Auflösung des Schrittmotors effektiv verdoppeln. Jedoch steigt der Stromverbrauch, und die Spannung muss ständig am Schrittmotor anliegen, weil der Rotor sonst bei einem halben Schritt in die letzte oder nächste Vollschrittstellung laufen würde.

Bei der Microschritt - Ansteuerung wird das Halbschrittverfahren weiter verfeinert, indem durch Pulsweitenmodulation des Stromes beider Spulen zwischen zwei Vollschritte beliebig viele Zwischenschritte eingefügt werden. Diese Ansteuerung hat jedoch keine große Bedeutung, da die erreichbare theoretisch beliebig hohe Auflösung von der Mechanik des Schrittmotors oft nicht nachvollzogen werden kann.

Schrittmotore können nicht unmittelbar vom Stand auf die gewünschte Geschwindigkeit beschleunigt werden. Auf Grund der Masseträgheit der rotierenden Komponenten würden einige der ersten Schritte übersprungen. Ebensowenig kann ein Schrittmotor von einer hohen Geschwindigkeit abrupt bremsen, da er durch die Trägheit etwas nachläuft. Ausserdem hat jeder Schrittmotor eine Resonanzfrequenz, bei dem sich die Vibrationen des Motors so stark 'aufschaukeln', dass der Motor langfristig sogar zerstört werden kann. Die Beschleunigungs- und Bremskurve sollte bei der Resonanzfrequenz ihren maximalen Anstieg aufweisen.

Beschleunigung - Fahrt - Bremsen

Das Ziel der hier vorgestellten Schrittmotor - Ansteuerung ist der Einsatz des Halbschrittverfahrens. Die Beschleunigungs- und Bremskurve soll mit Hilfe einer quadratischen Gleichung
s = mx2+n
berechnet werden.
zurück zum Anfang
Vollschritt - Ansteuerung
Vollschritt-Ansteuerung


Halbschritt - Ansteuerung
Halbschritt-Ansteuerung
Die benötigte Hardware zum Testen der Schrittmotor - Funktionalität ist einfach: es genügt ein Brückentreiber wie der L293D an den Datenpins des Parallelports. Eine Schreiboperation auf den Port 0x378 oder 0x278 (LPT1 und 2) setzt diese Datenpins. Eine Warnung am Rande: wer oft mit selbstgebauter Elektronik am Parallelport experimentiert, sollte das unbedingt an einer Schnittstellenkarte und nicht mit dem auf dem Motherboard integrierten Port tun. Wenn nicht die 'D'-Version des L293 mit integrierten Dioden zum Ableiten der Rückströme induktiver Lasten eingesetzt wird, sind diese Dioden zusätzlich einzubauen, da der Brückentreiber an sonsten schnell zerstört wird. Je nach Güte des Netzteils sind Pufferkondensatoren vorzusehen. Einige Parallelports benötigen Pullups an den Datenpins, da sie nicht genug Strom liefern können. In vielen Fällen kann darauf aber verzichtet werden.


Schaltplan und Aufbau des Testboards
Schaltplan Hardware-Aufbau

Das Programm

Zur Entwicklung braucht man als erstes einmal ein geeignetes Makefile, um die Compiler - Parameter nicht ständig neu angeben zu müssen. Dieses Makefile ist eine leichte Abwandlung der Makefiles, mit denen die RTLinux - Beispiele im examples-Verzeichnis erzeugt wurden. Der CFLAGS-Parameter '-g' bindet Debug - Symbole mit in den produzierten Binärcode ein und und kann für die finale Version entfernt werden. Es gibt drei Targets, die sich mit make, make clean und make modinst aufrufen lassen. Was diese Aufrufe produzieren, ist wohl selbsterklärend:

RTL_DIR = /usr/src/rtlinux-3.1
RTLINUX_DIR = /usr/src/rtlinux-3.1/linux
INCLUDE= -I/usr/src/rtlinux-3.1/linux/include -I/usr/src/rtlinux-3.1/include  \
  -I/usr/src/rtlinux-3.1/include/compat 
CFLAGS = -g -D__KERNEL__ -Wall -Wstrict-prototypes -fno-strict-aliasing -pipe \
  -mpreferred-stack-boundary=2 -march=i586 -DMODULE -g -D__RTL__              \
  -D_LOOSE_KERNEL_NAMES -O2 -I/usr/src/rtlinux-3.1/linux/include              \
  -I/usr/src/rtlinux-3.1/include -I/usr/src/rtlinux-3.1/include/compat        \
  -I/usr/src/rtlinux-3.1/include/posix
ARCH = i386
CC = gcc
CXXFLAGS = -D__KERNEL__ -Wall -Wstrict-prototypes -fno-strict-aliasing -pipe  \
  -mpreferred-stack-boundary=2 -march=i586 -DMODULE -g -D__RTL__              \
  -D_LOOSE_KERNEL_NAMES -I/usr/src/rtlinux-3.1/linux/include                  \
  -I/usr/src/rtlinux-3.1/include -I/usr/src/rtlinux-3.1/include/compat        \
  -I/usr/src/rtlinux-3.1/include/posix -fno-exceptions -fno-rtti

all:    
        clean \
        stepper.o

modinst:
        @echo "installiere Kernel-Module für RT-Linux"
        (cd /usr/src/rtlinux-3.1/; scripts/insrtl)

clean:
        @echo "Entferne alte Objektdateien:"
        rm -f *.o

Nun zum Programm. Zunächst werden einige Header eingebunden, statische Definitionen definiert und globale Variablen festgelegt. Die Schrittmotor - Bewegung gliedert sich in 4 aufeinanderfolgende Zustände: bei STATUS_STOP bewegt sich nichts, bei STATUS_SPEEDUP wird beschleunigt, bei STATUS_RUNNING wird die Geschwindigkeit gehalten und bei STATUS_SPEEDDN wird abgebremst.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/cons.h>
#include <asm/io.h>

#include <rtl_sched.h>
#include <rtl_fifo.h>

#include <linux/string.h>

// erster Parallelport:   0x378
// zweiter Parallelport:  0x278

#define LPT_PORT          0x278

// Enable-Signal liegt auf D7 des Parallelports
#define ENABLE           0x0080

#define STATUS_STOP           0
#define STATUS_SPEEDUP        1
#define STATUS_RUNNING        2
#define STATUS_SPEEDDN        3

// Globale Variablen
pthread_t controlthread;
pthread_t stepperthread;

int  status         = STATUS_STOP;
long changespeed = 0;

// Funktionsprototypen
void* stepper_control(void* arg);
void* stepper_out(void* arg);

Der nun folgende Thread hat die Aufgabe, Daten von /dev/rtf0 einzulesen und gemäß den übergebenen Anweisungen den Status weiterzusetzen und die Beschleunigungs- und Bremskurve zu errechnen. Die Kommandos werden einfach im Klartext an den FIFO geschickt, etwa in der Konsole mit "echo start > /dev/rtf0" Wer übrigens bei String- und überhaupt allen Operationen auf Arrays oder Puffern Funktionen ohne Angabe einer Maximallänge wie strcmp() statt strncmp() verwendet, braucht sich über Sicherheitslücken und bei unerwarteten Daten abstürzende Programme nicht zu wundern.

Die Veränderung der Geschwindigkeit des Schrittmotors geschieht indirekt über die globale Variable changespeed. Es ist zwar möglich, die Periodendauer des Ausgabe - Threads auch von diesem Steuerthread aus zu verändern, jedoch entstünden dadurch Unregelmäßigkeiten, da der Steuerthread ja nicht weiß, wieviel Zeit seit dem letzten Aufruf des Ausgabethreads vergangen ist.

// Steuer-Thread
void* stepper_control(void* arg) 
{
    char *buf[1024];
    int  count        = 255;

    pthread_make_periodic_np (pthread_self(), gethrtime(), 10000000);

    while(1)
    {
        // Daten vom FIFO einlesen
        int l = rtf_get(0, buf, 1024);
        
        // Daten vom FIFO interpretieren
        if(l>0)
        {
            if(strncmp("start",(char *)buf,5)==0 && status == STATUS_STOP)
            {
                rtl_printf("starting\n");

                // Thread starten
                pthread_wakeup_np(stepperthread);

                // Status weitersetzen                        
                status = STATUS_SPEEDUP;
                count = 255;
            }
            else if(strncmp("emerg",(char *)buf,5)==0 && status > STATUS_STOP)
            {
                rtl_printf("emergency stop\n");

                // Status weitersetzen                        
                status = STATUS_STOP;
            }
            else if(strncmp("stop",(char *)buf,4)==0 && status > STATUS_STOP)
            {
                rtl_printf("stopping\n");

                // Status weitersetzen                        
                status = STATUS_SPEEDDN;
            }
        }
        
        // Beschleunigungskurve berechnen (quadratische Gleichung)
        if(status == STATUS_SPEEDUP)
        {
            if(count == 0)
            {
                status = STATUS_RUNNING;
            }
            else
            {
                count=count-1;
                changespeed = (count*count*61)+1000000;
            }
        }
        else if(status == STATUS_SPEEDDN)
        {
            if(count == 255)
            {
                status = STATUS_STOP;
            }
            else
            {
                count=count+1;
                changespeed = (count*count*61)+1000000;
            }
        }
        
        // Warten bis zum nächsten Aufruf
        pthread_wait_np();
    }

    return 0;
}

Dieser Thread hat die Aufgabe, das Bitmuster für die Bewegung des Schrittmotors auf den Parallelport auszugeben. Dazu wird ein Zähler verwaltet, der über eine Modulo 8 - Division als Index auf ein Datenfeld mit den Bitmustern dient. Steht in der Variable changespeed ein Wert > 0, so wird die Periodendauer des Ausgabethreads verändert und die Variable auf 0 zurückgesetzt.
// Ausgabe-Thread
void* stepper_out(void* arg) 
{
    // Daten für die Ausgabe der Bitmuster für Halbschrittbetrieb
    int cnt = 0;
    const int val[] = {0x01,0x05,0x04,0x06,0x02,0x0A,0x08,0x09};

    // Thread als periodisch markieren und schlafenlegen
    pthread_make_periodic_np (pthread_self(), gethrtime(), 5000000);
    pthread_suspend_np(pthread_self());

    while(1)
    {
        // Wenn status auf STOP gesetzt, schlafen legen
        if(status == STATUS_STOP)
        {
            outb(0,LPT_PORT);
            pthread_suspend_np(pthread_self());
        }
        
        // Auf Änderung der Geschwindigkeit reagieren
        if(changespeed>0)
        {
            pthread_make_periodic_np (pthread_self(), gethrtime(), changespeed);
            changespeed = 0;
        }        
        
        // Bitmuster ausgeben, um den Stepper einen Schritt weiterzusetzen
        outb(val[cnt%8]|ENABLE,LPT_PORT);
        
        // Zähler auf das nächste Bitmuster
        cnt++;
        
        // Warten bis zur nächsten Ausgabe
        pthread_wait_np();
    }
    
    return 0;
}

Beim Laden eines Kernel - Moduls mit insmod wird die init_module()-Funktion vom Modul - Loader aufgerufen. Daher ist dies die richtige Stelle, um die beiden Threads zu starten.
int init_module(void)
{
    // Hello-Message ausgeben (dmesg)
    rtl_printf("Stepper Control is running!\n");

    // FIFO 0 (/dev/rtf0) initialisieren
    rtf_create(0, 1024*1024);

    // Threads initialisieren
    pthread_create(&stepperthread, NULL, stepper_out, (void*) 0);
    pthread_create(&controlthread, NULL, stepper_control, (void*) 0);

    return 0;
}

Beim Entladen eines Moduls mit rmmod wird diese Funktion aufgerufen. Sie hat die Aufgabe, die Threads zu stoppen und alle benutzten Ressourcen freizugeben.
void cleanup_module(void)
{
    rtl_printf("Stepper Control is shutting down!\n");

    // Threads anhalten
    pthread_delete_np(stepperthread);
    pthread_delete_np(controlthread);

    // Enable zurücksetzen
    outb(0,LPT_PORT);
        
    // Eingabe-FIFO anhalten
    rtf_destroy(0);
}


Das war das vollständige Programm. Man sieht, dass sich die Komplexität in Grenzen hält - auch mit nur rudimentären C - Kenntnissen und ohne große Programmiererfahrung unter Linux kommt man leicht zu ansprechenden Ergebnissen. Die POSIX - kompatiblen C - Standardbibliotheken, zu denen sich im Internet Beispiele für fast jede denkbare Problemstellung finden lassen, sowie die sehr gut dokumentierten RTLinux - Funktionen machen eine Entwicklung mit diesem System zum Vergnügen. Damit ist RTLinux auch für größere Projekte geeignet, ich werde also meinen Fräsroboter damit ansteuern können. Mehr dazu demnächst...
zurück zum Anfang

Bibliographie

www.ostermann-net.de     Details und Erläuterungen zu Schrittmotoren und deren praktischem Einsatz
www.fsmlabs.com     Finite State Machine Labs, Inc - der Maintainer von RTLinux
www.lfbs.rwth-aachen.de/~stefan/Praktikum/WS/Beispiele/rectangle.c     Beispiel zur Ausgabe eines Rechtecksignals auf den Parallelport mit RTLinux
www.linuxdoc.org/HOWTO/Kernel-HOWTO.html     Das Linux Kernel - HOWTO
rtlinux_stepper.zip     Quelltext des Programms

zurück zum Anfang

Seite zurück Startseite
Erik Buchmann
EMail an: Owner@ErikBuchmann.de