Browse Author: profhof

Formale Sprachen

Formale Sprachen sind künstliche Sprachen, die es Computern ermöglichen, Daten und Informationen zu verarbeiten. Eine formale Sprache L besteht aus einer Menge von Wörtern, die wiederum aus Zeichen des Alphabets ∑  der Sprache bestehen. 

Das Alphabet  ist hierbei die Menge der Zeichen, die in einem Wort  benutzt werden dürfen, wie zum Beispiel die Buchstaben von A bis Z und Umlaute im deutschen Alphabet.

Formale Sprachen werden durch Grammatiken erzeugt.

Zu diesem Thema finden sich viele gute Einführungen u.a. sei hier die sehr informative Seite von Studyflix E-Learning empfohlen (www.studyflix.de), auf deren Inhalte hier stellenweise Bezug genommen wird.

Grammatiken

Nichtterminale Symbole können durch andere Symbole ersetzt werden, während terminale Symbole unveränderlich sind. Das heißt, man erkennt die Symbole an sogenannten Produktionsregeln. Diese Regeln beschreiben, aus welchen Zeichen welche anderen Zeichen entstehen können.

Formale Grammatiken beschreiben und erzeugen durch Symbole und Regeln formale Sprachen. Die Regeln definieren dabei, wie neue Symbole aus bereits bestehenden entstehen können. Bei den Symbolen unterscheidet man zwischen terminalen und nichtterminalen:

Chomsky Hierarchie
Quelle: www.studyflix.de

Man unterscheidet Grammatiken anhand der möglichen Einschränkungen ihrer Produktionsregeln. Die sogenannte Chomsky-Hierarchie stellt eine Hierarchie von Klassen formaler Grammatiken dar, welche formale Sprachen erzeugen. Darin werden vier verschiedene Arten von Typ-0 bis Typ-3 unterschieden, wobei Typ-0 die Sprache überhaupt nicht einschränkt, während Typ-3 die Grammatik sehr stark einschränkt.

Quelle: www.studyflix.de

Die Typ-0-Grammatik wird auch Chomsky-Grammatik oder Phasenstrukturgrammatik genannt. Allgemein kann man sagen, dass alle formalen Grammatiken mindestens vom Typ-0 sind, da hier keine Einschränkungen gestellt werden. Es kann also aus jedem Wort ein beliebiges anderes Wort entstehen.

Typ-1-Grammatiken, auch kontextsensitive Grammatiken genannt. Sie erweitern die Typ-0-Grammatik um eine Längenbeschränkung. Diese Beschränkung sagt aus, dass bei einer Produktionsregel \omega_1\rightarrow\omega_2 immer gelten muss, dass die Länge von \omega_2 mindestens der Länge von \omega_1 entspricht. Es gibt eine einzige Ausnahme: aus einem Symbol S darf das leere Wort \varepsilon gebildet werden.

Typ-2 Grammatiken (kontextfreie Grammatiken), sind zusätzlich dadurch eingeschränkt, dass jede Regel der Grammatik auf der linken Seite genau ein nichtterminales Symbol, und auf der rechten Seite eine beliebige Kombination aus terminalen und nichtterminalen Symbolen enthalten muss. Die Kombination muss hierbei immer mindestens ein Element enthalten.

Typ-3-Grammatiken, auch reguläre Grammatiken genannt. Sie sind im Vergleich zu Typ-2-Grammatiken zusätzlich dadurch eingeschränkt, dass auf der rechten Seite der Produktionsregeln genau ein Terminalsymbol sein muss und optional ein Nichtterminalsymbol sein kann. Je nachdem, auf welcher Seite des Terminalsymbols das nichtterminale Symbol ist, spricht man von einer linksregulären oder einer rechtsregulären Grammatik. Typ-3-Grammatiken erzeugen die regulären Sprachen und werden von Endlichen Automaten erkannt.

Reguläre Grammatiken und Endliche Automaten

Die Reguläre Grammatik stellt eine Typ 3 Grammatik der Chomsky-Hierarchie dar und erzeugt reguläre Sprachen. Es ist ein 4-Tupel, bestehend aus der Menge der Terminalsymbole, der Nichtterminale und der Produktionen, sowie einem Startsymbol.

Die Grammatik wird über dieses 4-Tupel erzeugt:

G=(N,\ T,\ P,\ S)

N ist dabei die Menge der Nichtterminale oder auch Variablen und wird mit Großbuchstaben bezeichnet.

N=\left\{B,C,\ D\right\}

T ist die Menge der Terminalsymbole in Kleinbuchstaben.

T=\left\{a,b,\ c\right\}

S ist das Startsymbol und als Variable in N enthalten. P ist schließlich die Menge der Produktionsregeln.

Die Definition beschreibt somit zum einen die Grundelemente der Sprache, also Terminalsymbole, die beispielsweise bei Programmiersprachen für Schlüsselworte stehen. Aus diesen können dann Sätze zusammengestellt werden, die auf Produktionsregeln, bzw. Konstruktionsregeln basieren.

Beispiel

Reguläre Grammatiken erzeugen reguläre Sprachen, deshalb gibt es für jede reguläre Sprache immer mindestens eine reguläre Grammatik. Zur besseren Verständlichkeit betrachten wir die folgende Sprache als „Reguläre Grammatik Beispiel“:

    \[L = \{0^n1^{2m}|n>0, m\ge0 \}\]

Sie enthält alle Wörter, die mit einem bis n Nullen beginnen und mit keiner oder einer geraden Anzahl Einsen enden. Wenn man diese Sprache auf eine wie eben beschriebene reguläre Grammatik zurückführen kann, dann ist sie regulär.

Gestartet wird mit dem Startsymbol S. Dabei wird versucht zunächst das kleinstmögliche Wort zu bilden. Das wäre einfach Null.

Mit der ersten Produktionsregel „S wird umgewandelt in Null“ bekommt man genau dieses Wort.

S\ \rightarrow0

Man benötigt aber eine Möglichkeit mehr als eine 0 zu erzeugen. Das gelingt, indem an die Null einfach erneut das Startsymbol angefügt wird:

S\ \rightarrow0S

Nachdem eine Null erzeugt wird, befindet man sich wieder in S und kann dadurch so viele beliebige Nullen erschaffen. Im nächsten Schritt muss man das Wort entweder beenden oder mit den Einsen anfangen. Dabei kann man die Produktionsregel einfach erweitern:

S\ \rightarrow0S\ \left|\ 1S\ \right|\ \varepsilon

S wird umgewandelt in Null S oder 1 S oder Epsilon.

Doch Vorsicht! Mit dieser Regel kann man das leere Wort  S\ \rightarrow\varepsilon erzeugen. Oder eines, das direkt mit einer 1 beginnt: (S\ \rightarrow1S). Beides darf in dieser Sprache aber nicht passieren. Deshalb muss zwischen dem Teil des Wortes, das Nullen enthält und dem Rest unterschieden werden. Dafür braucht man eine weitere Variable.

S\ \rightarrow0S\ |\ 0B

Mit dieser Regel können viele Nullen erschafft werden, dabei muss aber mindestens eine erzeugt werden, bevor man sich dem nächsten Teil des Wortes in B widmen kann. Von B aus beendet man entweder mit Epsilon oder erzeugt Einsen.

B\ \rightarrow1B\ |\ \varepsilon

Wenn das Wort Einsen besitzt, müssen diese in gerader Anzahl vorhanden sein. Dies wird mit einer Schleife erreicht:

B\ \rightarrow1C\ |\ \varepsilon
C\ \rightarrow1B

Dabei gelangt man von B mit einer 1 nach C und von dort mit einer weiteren 1 nach B zurück. Die Produktionsregeln erzeugen nun die beschriebene Sprache.

S\ \rightarrow0S\ |\ 0B
B\ \rightarrow1C\ |\ \varepsilon
C\ \rightarrow1B

Endliche Automaten

Grundsätzlich gibt es für jede reguläre Grammatik einen zugehörigen deterministischen Automaten. Dieser wird durch einen nichtdeterministischen endlichen Automaten erstellt, indem aus den Nichtterminalsymbolen ein Zustand erstellt wird und zusätzlich aus jeder Konstruktionsregel einen Übergang erzeugt.

Im Gegenzug gibt es natürlich auch in jedem deterministischem endlichen Automaten eine reguläre Grammatik. Allgemein gilt: Jede Sprache, die endliche Automaten akzeptieren, wird von einer regulären Grammatik erzeugt.

Deterministische endliche Automaten (DEA)

Deterministische endliche Automaten – kurz DEA (Informatik) oder DFA (Englisch: deterministic finite state machine)– sind endlichen Automaten . Gibt man nun eine Eingabe, wobei nur Zeichen enthalten sein können, die im Eingabealphabet stehen, in den Automaten ein, dann passiert für jede Eingabe ein Zustandsübergang. Hierbei gilt, dass ein determinisitischer endlicher Automat immer eindeutig ist, bei welcher Eingabe welcher Zustandsübergang ausgeführt wird.

Die Arbeitsweise eines DEA ist hierbei so simpel wie genial: Nehmen wir an, es liegt ein Eingabewort vor, das nur aus Zeichen des Eingabealphabets besteht. Der Automat startet nun in seinem Startzustand, den wir hier Z_0 nennen. Nun liest der Automat das erste Zeichen des Wortes ein, wodurch sich der Zustand des Automaten ändert. Dafür brauchen wir die Übergangsfunktion: Der Automat betrachtet den aktuellen Zustand Z_0 und das eingelesene Zeichen. Er erfährt durch die Übergangsfunktion den neuen Zustand, beziehungsweise den Folgezustand. Dies geschieht so lange, bis das Wort vollständig eingelesen ist. Befindet sich der Automat nun in einem Endzustand, dann wird das Eingabewort akzeptiert. Ist der Automat jedoch in einem normalen Zustand, wird das Wort verworfen.

Beispiel-Anwendung

Als Beispiel soll ein Automat entwickelt werden, mit dessen Hilfe Telefon-Nummern erkannt werden sollen. Da es sich um ein einfaches Beispiel handeln soll, wird hierbei auf das erkennen internationaler Nummern verzichtet. Korrekte Telefon-Nummern sollen von folgendem Aufbau sein:

Die Nummer beginnt mit einer “0”  gefolgt von beliebiger Ziffer aus der Folge 1..9 , der dann wieder Ziffern aus 0…9 folgen können. Anschliessend folgt ein “/” gefolgt von einer beliebigen Ziffer 1..9 der dann beliebig viele Ziffern von 0 …9 folgen dürfen.

Ein entsorgender endlicher Automat hat dann folgende Gestalt:

Ein wesentlicher Vorgang von EA ist das man diese sehr effizient in Programmcode überführen kann. Im folgenden ist das am Beispiel der Telefon-Nummern Erkennung dargestellt:

Quellcode zum Telefon-Nummern Erkennungs-Beispiel

Drehstellgeber

Das Funktionsprinzip: Im Innern eines Encoders (Impulsgebers) sind zwei Taster integriert. Sie schalten um einen halben Klick Phasenverschoben. Detektiert man auf dem Kanal 1 (also vom ersten Taster) einen Wechsel von LOW zu HIGH prüft man, ob Kanal 2 LOW ist. Ist dieser LOW, wurde in die eine Richtung gedreht, ist er HIGH, wurde er in die andere Richtung gedreht.

Das Ganze funktioniert natürlich auch in die andere Richtung: Detektiert man auf dem Kanal 1 (also vom ersten Taster) einen Wechsel von HIGH zu LOW prüft man, ob Kanal 2 HIGH ist. Ist dieser HIGH, wurde in die eine Richtung gedreht, ist er LOW, wurde er in die andere Richtung gedreht.

Dazu muss man zu Beginn festlegen wie man die beiden möglichen Drehrichtungen benennen möchte, da sonst Verwirrungen entstehen können.

int encoderPinA = 2;
int encoderPinB = 3;
int encoderPos = 0;
int encoderPinALast = LOW;
int encoderPinANow = LOW;

void setup() {
  pinMode (encoderPinA, INPUT_PULLUP);
  pinMode (encoderPinB, INPUT_PULLUP);
  Serial.begin (115200);
}

void loop() {
  encoderPinANow = digitalRead(encoderPinA);
  if ((encoderPinALast == HIGH) && (encoderPinANow == LOW)) {
    if (digitalRead(encoderPinB) == HIGH) {
      encoderPos++;
    } else {
      encoderPos--;
    }
    Serial.println(encoderPos);
  }
  encoderPinALast = encoderPinANow;
}

Temperatursensoren

DS18B20

Beim DS18B20 handelt es sich um einen digitalen Temperatursensor im TO92 Gehäuse, der über einen One-Wire-Bus angesprochen wird. Auf diese Weise ist es möglich mehrere Sensoren an einem Mikrocontroller-Pin anzuschließen. Der Sensor besitzt eine Auflösung von 12 Bit und eine Messgenauigkeit von ±0.5°C im Messbereich von -55°C bis +125°C.

PIN Belegung (Quelle: AZ-Delivery.com)

Für die Funktion wird der Widerstand R1 = 4,7kOhm (Pullup-Widerstand genannt) zwischen Vcc und dem Daten Pin benötigt, weil er die Spannung bei fehlendem Signal auf die Versorgungsspannung hochzieht), was im Datenblatt so nicht gleich ersichtlich ist.

Der serielle Datenbus (1-Wire-Bus) und das vom Sensor verwendete Protokoll erfordern erheblichen Programmieraufwand, daher wird in den meisten Fällen auf vordefinierte Funktionsbibliotheken zurück gegriffen.

DHT 11 und DHT 22

Der DHT22 ist ein zuverlässiger Sensor zum ermitteln von Temperatur und Luftfeuchtigkeit. Da der Sensor sowohl mit 3,3V als auch 5V betrieben werden kann, eignet er sich zum Anschluss an alle gängige Boards

DHT 22 Sensor PIN Belegung (Quelle: Adafruit.com)

ESPLORA Board

Der Arduino Esplora ist ein Board aus den Anfängern der Arduino Ära, das heute nicht mehr gebaut wird. Die Besonderheit dieses Boards, ist die Ausstattung mit unterschiedlichen Bausteinen. Für Eingaben gibt es einen Joystick, vier Tasten, einen Lichtsensor, einen Schieberegler, ein Mikrofon, einen Temperatursensor und einen Beschleunigungsmesser. Für die Ausgänge gibt es einen Summer und eine dreifarbige LED. Auf den ersten Blick sieht es aus wie ein Videospiel-Controller.

ESPLORA Board (Quelle: Arduino.cc)

Infolgedessen unterscheidet sich die Programmierung etwas von der für andere Arduino-Boards. Es verfügt über eine eigene Bibliothek, die das Lesen von den Eingangssensoren und das Schreiben auf die Ausgangsaktoren erleichtert. Informationen zur Verwendung der Bibliothek finden Sie in diesem Handbuch und auf den Referenzseiten der Esplora-Bibliothek.

Der Arduino Esplora verfügt über eine Reihe von Funktionen für die einfache Verbindung mit den auf der Platine montierten Sensoren und Aktoren. Auf die Funktionen kann über die Esplora-Klasse zugegriffen werden. Die Bibliothek bietet einfachen Zugriff auf die Daten von den integrierten Sensoren und bietet die Möglichkeit, den Status der Ausgänge zu ändern.

Esplora Temperatur Sensor

Das folgende Programm zeigt, wie man den Temperatursensor des Esplora verwenden kann. Die Funktion Esplora.readTemperature () ruft den Wert vom Temperatursensor ab. Je nach Ihrer Wahl erhalten Sie die Temperatur in Grad Celsius oder Grad Fahrenheit. Es wird ein Parameter benötigt, DEGREES_C für Celsius oder DEGREES_F für Fahrenheit.

#include <Esplora.h>

void setup() {
  Serial.begin(9600);      // initialize serial communications with your computer
}

void loop() {
  // read the temperature sensor in Celsius, then Fahrenheit:
  int celsius = Esplora.readTemperature(DEGREES_C);
  int fahrenheit = Esplora.readTemperature(DEGREES_F);

  // print the results:
  Serial.print("Temperature is: ");
  Serial.print(celsius);
  Serial.print(" degrees Celsius, or ");
  Serial.print(fahrenheit);
  Serial.println(" degrees Fahrenheit.");
  Serial.println("     Fahrenheit = (9/5 * Celsius) + 32");

  delay(1000);
}

Esplora Digital Write

Um Daten Auszugeben, können die üblichen Funktionen verwendet werden. Auf dem oben abgebildeten “inoffiziellen” Esplora Diagramm kann man entnehmen, dass ein Ausgang über den Digitalen Pin 3 am Thinkerboard Stecker möglich ist.

// include the Esplora library
#include <Esplora.h>

void setup() {

pinMode(3,OUTPUT);
digitalWrite(3,HIGH);
}

void loop() {
}

DC Motor-Steuerung

Elektromotoren können relativ einfach gesteuert werden. Motorsteuerung bedeutet im einfachsten Fall, den Motor in Bewegung zu versetzen. Dies geschieht in dem die Wicklungen des Motors mit Spannung versorgt werden. Dabei gibt es nur zwei Zustände: der Motor ist mit Spannung versorgt und dreht sich oder im anderen Fall eben nicht.

Um die Richtung des Motors zu wechseln, muss der Stromfluss in der Spule umgekehrt werden. Um dies ohne manuelles umpolen der Spannungsquelle zu erreichen, ist eine spezielle Schaltung erforderlich, die auf geschickte Art das automatische Umpolen ermöglicht. Die Schaltung hat ihren Namen von der Form wie die Schalter angeordnet sind. Da sie wie ein „H“ aus sieht wird sie als H-Schaltung oder H-Brücke bezeichnet. Eine solche H-Brücke sieht schematisch so aus:

Die rote Linie zeigt den Stromweg für die jeweilige Drehrichtung. Sind die Schalter 1 und 4 geschlossen, so dreht sich der Motor rechts herum. Schliesst man die Schalter 2 und 3 dreht sich der Motor in die andere Richtung. Wobei zu beachten ist, dass nicht alle theoretisch möglichen Schalterstellung zugelassen werden, um beispielsweise Kurzschlüsse zu vermeiden. Die Betriebsspannung U ist die Spannung für den Motor, die meist unabhängig von der Logik ist und auch höher gewählt werden kann als 12 V.

Steuerung der Motor-Geschwindigkeit

Die Geschwindigkeit eines Motors ist direkt proportional zur angelegten Spannung. Dies ermöglicht eine Geschwindigkeitsregulierung, indem eine Puls-Weiten-Modulation (PWM) verwendet wird.

Pulsweitenmodulation oder PWM ist eine Technik, die digitale Methoden verwendet, um analoge Ergebnisse zu erhalten. Bei der Pulsweitenmodulation nimmt man ein Rechtecksignal mit einer festen Frequenz und variiert die Breite der jeweiligen Pulse. Den Abstand zweier aufeinander folgender Pulse bezeichnet man als Periodendauer, die Breite des aktiven Pulses als Pulsweite. Das Verhältnis von Pulsweite zu Periodendauer bezeichnet man auch als “Duty Cycle”(also die Zeit, in der der Pin im Dienst ist).

Programmtechnisch kann ein PWM-Signal wie in der Graphik gezeigt erzeugt werden, und beispielsweise zur Geschwindigkeitsregelung an einem Gleichstrommotor verwendet werden. Dies ist möglich, weil die Geschwindigkeit eines Motors direkt proportional zur angelegten Spannung ist. Somit ermöglicht die Veränderung der Spannung über das erzeugte Verhältnis T1 zu T1+T2 die Geschwindigkeitsregulierung am Motor.

Standardmäßig bietet der Arduino mit dem Befehl AnalogWrite(…) die Möglichkeit, PWM-Signale mit einer Auflösung von 8 Bit und einer PWM-Frequenz von 490 Hz (Pin D3, D9, D10 und D11) bzw. 980 Hz (Pin D5 und D6) auszugeben. Die Funktion AnalogWrite sendet pseudo-analoge Werte mittels einer hardwarebasierten Pulsweiten Modulation (PWM) an den adressierten Ausgangspin. Diese arbeitet nach folgendem Prinzip: Ein Wert 0 generiert eine gleichmäßige Spannung von 0 Volt; Ein Wert von 255 generiert eine gleichmäßige Spannung von 5 Volt an einem festgelegten PIN. Für Werte zwischen 0 und 255 wechselt der PIN sehr schnell zwischen 0 und 5 Volt – je höher der Wert, desto länger ist der PIN auf HIGH (5 Volt).

In der nachfolgenden Tabelle werden einige markante Tastwerte den entsprechenden PWM Werten gegenüber gestellt. Würde man beispielsweise analogWrite(5, 64); eingeben, so würde am Pin 5 ein Signal mit einem Tastgrad von 25% ausgegeben werden.

Tastgrad0 %25%50%75%100%
PWM-Wert064128191255

Ansteuerung eines DC Motors

In der Praxis verwendet man keine einzelnen Transistoren sondern hochintegrierte Schaltkreise, die sämtliche Bauteile für eine Motorsteuerung enthalten. Ein Beispiel wäre der Baustein L293 D, der über 600mA Strom liefern kann. Daneben gibt es noch weit leistungsstärkere wie den L298 D der mit über 2A Motoren steuern kann. Im nachfolgenden Blockschaltbild ist der prinzipielle Aufbau schematisch dargestellt:

Blockschaltbild des IC L293 (Quelle www.ti.com)

Die Ansteuerung eines Motors ist damit relativ unkompliziert machbar. Wichtig ist dabei auf die unterschiedlichen Spannungsversorgungen zu achten. Der Baustein kann ohne Probleme mit 5V Spannung vom Arduino betrieben werden. Da die zu steuernden Motoren aber meist größere Spannungen benötigen, besteht die Möglichkeit eine externe Spannungsquelle an PIN 8 des Bausteins anzuschliessen. Der Masse Anschluss wird dagegen von beiden Spannungen verwendet.

Die Steuerung des Motors erfolgt über die beiden Eingänge IN1 und IN2 die mit den digitalen Ausgängen des Arduinos verbunden werden. Dabei können folgende vier Zustände auftreten:

Eingang IN 1Eingang IN 2Motor Verhalten
LOWLOWMotor Stoppt
LOWHIGHMotor dreht sich nach Links
HIGHLOWMotor dreht sich nach rechts
HIGHHIGHMotor stoppt

Eine elementare Steuerung mit dem Arduino sieht im einfachsten Fall so aus:

const int PIN_A = 7;
const int PIN_B = 8;

void setup () {
  Serial.begin(9600); 
  pinMode(PIN_A , OUTPUT);
  pinMode(PIN_B , OUTPUT); 
 
  // Motor nach rechts drehen
  digitalWrite(PIN_A, LOW); 
  digitalWrite(PIN_B, HIGH);

  // Motor nach links drehen
   digitalWrite(PIN_A, HIGH); 
   digitalWrite(PIN_B, LOW);
void loop() { }

Veranschaulichung der Ableitung einer Funktion

Viele Schüler und Studenten tun sich etwas schwer, wenn es darum geht sich die Ableitung einer Funktion vorzustellen. Sie können zwar häufig durch die Anwendung der Ableitungsregeln, die Funktion ableiten, oft ist es aber mehr als nur hilfreich, wenn man auch versteht, was sich hinter dieser elementaren Operation verbirgt. Im folgenden soll mit Hilfe von Mathematica vorgestellt werden, wie die Ableitung-Operation visualisiert werden kann.

Ableiten heisst die Steigung eines Punktes P auf einem Graphen G zu bestimmen. Man lernt auch, dass man sich diesen Vorgang durch Anlegen einer Tangente an diesen Punkt vorstellen kann.

Um das Vorstellungsproblem anschaulich zu machen, schaut man sich am einfachsten eine Funktion und die zugehörige Ableitung-Funktion an.In Mathematica geht das über folgende Anweisungen:

f[x_] := x^3 - 9 x + 5;
Plot[{f[x], f'[x]}, {x, -4, 4}]

Wir sehen in der Graphik in Blau die Ursprungsfunktion und in Orange die Ableitungsfunktion. Dabei wird nun schon sehr deutlich, dass es nicht gerade intuitiv möglich ist, sich die Steigung (oder die Tangente mit der entsprechenden Steigung) auf dem blauen Graphen vorzustellen.

Relativ einfach kann man sich mit Mathematica nun erstmal eine Punkt-Menge auf dem Graphen vorstellen, an denen wir zur Veranschaulichung dann auch die Tangente anlegen werden. Nun aber zuerst zur Punkt-Menge.

Als erstes benötigen wir eine Liste mit Koordinaten der Punkte die wir auf dem Graphen darstellen möchten. Dazu nutzen wir folgenden Funktionsaufruf, der eine Liste über die Table-Funktion von x und zugehörigen f(x) Werten erzeugt:

Punktliste = Table[{x, f[x]}, {x, -4, 4, 0.25}]
Das ergibt dann folgende Liste:
{{-4., -23.}, {-3.75, -13.9844}, {-3.5, -6.375}, {-3.25, -0.078125}, \
{-3., 5.}, {-2.75, 8.95313}, {-2.5, 11.875}, {-2.25, 13.8594}, {-2., 
  15.}, {-1.75, 15.3906}, {-1.5, 15.125}, {-1.25, 14.2969}, {-1., 
  13.}, {-0.75, 11.3281}, {-0.5, 9.375}, {-0.25, 7.23438}, {0., 
  5.}, {0.25, 2.76563}, {0.5, 
  0.625}, {0.75, -1.32813}, {1., -3.}, {1.25, -4.29688}, {1.5, \
-5.125}, {1.75, -5.39063}, {2., -5.}, {2.25, -3.85938}, {2.5, \
-1.875}, {2.75, 1.04688}, {3., 5.}, {3.25, 10.0781}, {3.5, 
  16.375}, {3.75, 23.9844}, {4., 33.}}

Mit der Epilog-Funktion können diese Koordinaten dann als Punkte auf dem Graphen dargestellt werden:

Plot[f[x], {x, -4, 4}, 
 Epilog -> {PointSize[0.015], Hue[1], Map[Point, Punktliste]}]

Nun stellt sich die Frage, wie man für alle diese Punkte eine Tangentengleichung bestimmt werden kann. Ausgangspunkt ist die bekannte Geradengleichung: y = m x + b. Wobei m die Steigung beschreibt und b den y-Achsenabschnitt also den Abstand zur x-Achse bezeichnet.

Allgemein gilt für die Tangente an einem bestimmten Punkt a eines Graphen der Funktion f(x) folgende Gleichung:

    \[y_t(x) = f(a) + f'(a) (x-a)\]

Mit Mathematica lässt sich das problem relativ einfach beschreiben. Man definiert die Ausgangsfunktion f(x) und die allgemeine Tangentengleichung t(x) und den Punkt a und lässt sich das Ganze dann über die Plot-Funktion anzeigen.

Möchte man dies nun für alle Punkte, mit einer kürzeren Tangente darstellen geht man wie folgt vor:

Für unsere Tangentengleichung kennen wir bereits die Steigung m, diese ergibt sich aus der Ableitung f'(x) an einer bestimmten Position x.

Der Parameter b (Y-Achsen-Abschnitt) bestimmen wir über den Funktionswert des Graphen.

Punktliste1 = Table[{x, f[x]}, {x, -4, 4, 0.75}];
Plot[f[x], {x, -4, 4}, 
 Epilog -> {PointSize[0.015], Hue[1], Map[Point, Punktliste1]}]

Die nun fehlende Ziel-Koordinate bestimmen wir über folgende Überlegung: Ausgangs-Koordinate ist der Punkt x_0 und der zugehörige Funktionswert f(x_0). Die Steigung des anzulegenden Pfeils entspricht der Ableitung f'(x_0) damit lässt sich nun die Ziel-Koordinate berechnen in dem man auf der x-Achse ein Stück h dazu addiert und y-Position über die Steigung und den y-Achsen-Abschnitt berechnet.

Tangentenliste = 
 Table[{{x, f[x]}, {(x + 0.25), f[x] + f'[x]*0.9}}, {x, -4, 4, 0.25}]

Mit diesen Informationen sind wir in der Lage die gewünschte Darstellung berechnen zu lassen:

Show[Plot[f[x], {x, -4, 4}, 
  Epilog -> {PointSize[0.015], Hue[1], Map[Point, Punktliste]}], 
 Graphics[Map[Arrow, Tangentenliste]], Plot[f'[x], {x, -4, 4}]]

Eine weitere Möglichkeit…mit Hilfe der Manipulate-Funktion

Manipulate bietet die Möglichkeit bestimmte Parameter zu verändern. Damit lassen sich insbesondere Zusammenhänge sehr einfach visualisieren. Im folgenden soll dies am Beispiel der Tangente sprich der Ableitung an der Sinus-Funktion vorgestellt werden:

f[x_] = Sin[x];
l[x_, a_] := 
 f[a] + f'[a] (x - a) /; 
  a - 1/(Sqrt[1 + (f'[a])^2]) <= x <= a + 1/(Sqrt[1 + (f'[a])^2])
Manipulate[
 Plot[{f[x], l[x, a]}, {x, 0, 2 \[Pi]}, PlotRange -> {-1.5, 1.5}], {a,
   0, 2 \[Pi]}]

Das ergibt dann in Abhängigkeit des Parameters a, der über die Manipulate-Funktion sowohl manuell als auch automatisch verändert werden kann, folgende Ausgabe:

Damit dürfte es möglich sein, die Ableitung einer Funktion in 2 Dimensionen jedem Interessierten näher zu bringen. Hat man das einmal verstanden ist der Weg in die dritte Demission und zu den partiellen Ableitungen nicht mehr weit.

Wii Nunchuk Controller als Steuerelement am Arduino

In Bearbeitung …

Der Nunchuck Controller ist ein Eingabe-Instrument für die bekannte Wii-Spieleconsole. Er verfügt über 2 Druckknöpfe, einen Joystick und einen eingebauten 3-Achsen-Beschleunigungssensor. Dieser Controller ist aufgrund der hohen Verbreitung für wenig Geld erhältlich, sehr robust und verfügt über eine gut dokumentierte I2C-Schnittstelle, die es erlaubt, diesen mit dem Arduino zu verbinden. Somit kann der Controller für die verschiedensten Steuerungsaufgaben zweckentfremdet werden.

Älterer Wii-Nunchuk-Controller

Da viele Maker diesen Controller bereits in Ihren Projekten verwenden, finden sich viele Programm-Bibliotheken im Internet, die eine einfache Verwendung erlauben. Die hier verwendete Bibliothek stellt folgende Funktionen bereit:

void nunchuk_setpowerpins()void nunchuk_init()
int nunchuk_get_data()void nunchuk_calibrate_joy()
inline unsigned int nunchuk_zbutton()inline unsigned int nunchuk_cbutton()
inline int nunchuk_joy_x()inline uint16_t nunchuk_accelx()
inline int nunchuk_cjoy_x()inline uint16_t nunchuk_accely()
inline int nunchuk_cjoy_y()inline uint16_t nunchuk_accelz()
inline int nunchuk_joyangle()void nunchuk_calibrate_accelxy()
inline int nunchuk_rollangle()void nunchuk_calibrate_accelz()
inline int nunchuk_pitchangle()
Funktionen der wiinunchuk-Bibliothek

Die Bibliothek bereitet die Daten vom Controller so auf, dass diese über eine Anweisung ausgelesen werden können. Insgesamt benötigt man einen Speicherpuffer mit 75 Zeichen, in die sämtliche Sensorwerte geschrieben werden. Wie das geht, zeigt das erste Beispiel-Programm, dass nur die Joystick-Position und die Zustände der beiden Schalter C und Z ausliest.

#include <Wire.h>
#include <wiinunchuck.h>

void setup() { 
  Serial.begin(9600);
  nunchuk_init(); // Nunchuk initialisieren, Joystick auf Mittelposition
  delay(100);
  nunchuk_calibrate_joy();
  delay(100);
}
 
void loop() {
  nunchuk_get_data();   // Daten (6 Byte) vom Nunchuk Controller auslesen 
  char buffer[25];
  sprintf(buffer, "X:%3d Y:%3d Z:%1d C:%1d", 
          nunchuk_cjoy_x(), nunchuk_cjoy_y(), 
          nunchuk_zbutton(), nunchuk_cbutton() );
  Serial.println(buffer);   // Zusammengesetzten String an seriellen Monitor schicken
  delay(50);
}

Der Nunchuk wird über ein einfaches I²C Protokoll angesprochen. Mit Hilfe eines Befehles können wir alle Daten aus dem Nunchuk lesen. Die Daten kommen dabei als Paket von 6 Bytes in folgendem Format:

Byte 1: Joystick – X
Byte 2: Joystick – Y
Byte 3: ACC X (9..2)
Byte 4: ACC Y (9..2)
Byte 5: ACC Z (9..2)
Byte 6: ist aufgeteilt in Bits
Bit 0: Z-Taste
Bit 1: C-Taste
Bit 2,3: fehlenden Bits 0,1 vom ACC X
Bit 4,5: fehlenden Bits 0,1 vom ACC Y
Bit 6,7: fehlenden Bits 0,1 vom ACC Z

Um die Daten mit einem Arduino verarbeiten zu können, muss der Controller natürlich mit dem Arduino verbunden werden. Es gibt sechs Steckplätze, wobei nur die vier Äusseren belegt sind.

Wii-Steckerbelegung, die Farben können allerdings variieren.

Es gibt Adapter, aber aufgrund der Tatsache, dass ich meine nicht mehr anderswo benötige, habe ich den Stecker einfach abgeschnitten und Arduino-kompatibel mit Steckbrücken versehen.

Zeiger Programmierung

In der Sprache C existiert ein Konstrukt das als Zeiger oder Pointer bezeichnet wird. Zeiger repräsentieren eine besondere Art von Variablen. Ihr Inhalt ist eigentlich nebensächlich. Viel interessanter ist, dass sie auf andere Variablen zeigen können. Man betrachtet also nicht die Zeigervariable selbst, sondern über die Zeigervariable auf den Inhalt einer anderen Variablen.

Diese Indirektion ist nicht ganz leicht zu verstehen und bietet auch bei erfahrenen Programmieren immer wieder Anlass zur Verwirrung. Aus diesem Grund soll im folgenden Beitrag die Funktionsweise eines Zeigers erklärt werden.

Variablen

Eine Variable wird zum speichern von Informationen verwendet. Sie muss üblicherweise deklariert werden. D.h. man ordnet ihr einen eindeutigen Namen und einen Datentyp zu. Diese Informationen werden vom Compiler dann in einer sogenannten Symboltabelle abgelegt und verwaltet.

Nachdem man beispielsweise folgende Deklaration gemacht hat:

int myVar;

Wird in der Symboltabelle folgender Eintrag vorgenommen:

Bei diesem Vorgang fordert der Compiler vom Speichermanagement des Systems eine Adresse an, wo die damit zusammenhängenden Informationen dann gespeichert werden. Diese Speicheradresse wird als I-Wert bezeichnet, und gibt an, wo sich die Variable im Speicher befindet also (I-Wert = Standort).

Wenn wir einer Variablen nun einen konkreten Wert zuweisen, navigieren wir direkt zum Speicherort der Variablen im Speicher (dem I-Wert) und aktualisieren den Speicher an dieser Adresse mit dem neuen Wert. Die Daten, die tatsächlich im Speicher gespeichert sind, werden als R-Wert (R-Wert = Registerwert ) bezeichnet. Die Anzahl der dazu benötigten Speicherplätze hängt massgeblich vom Typ der Variablen ab.

Zeiger

Nachdem wir uns nun mit den Variablen und ihrer Funktionsweise vertraut sind, können wir uns dem Zeiger-Konstrukt zu wenden. Einfach ausgedrückt ist ein Zeiger nichts anderes als eine Variable, die auf die Speicheradresse einer anderen Variablen verweist. 

Unter Verwendung der soeben erlernten Terminologie ist ein Zeiger somit eine Variable, deren R-Wert der I-Wert einer anderen Variablen ist.

Die Deklaration einer Zeiger-Variable ist recht einfach. Um eine Zeigervariable zu definieren, wird zunächst der Typ angegeben, auf den der Zeiger zugreifen kann. Es folgt ein Stern und dann der Name der Variablen:

int *myPointer;

Der Typbezeichner (in diesem Fall int) muss mit dem Datentyp der Variablen übereinstimmen, mit der der Zeiger verwendet werden soll. Das Sternchen zeigt dem Compiler an, dass myPointer ein Zeiger ist. Da Leerzeichen in C keine Rolle spielen, kann das Sternchen an einer beliebigen Stelle zwischen dem Typbezeichner und dem Namen der Zeigervariablen platziert werden, so dass manchmal auch Folgendes angezeigt wird: int * myPointer, int * myPointer usw.

Ein Zeiger, der definiert ist, aber nicht auf irgendetwas zeigt, ist für sich genommen ein ziemlich sinnloser Zeiger. Um auf die Speicheradresse einer anderen Variablen zu verweisen, müssen wir dem Zeiger natürlich die Speicheradresse dieser Variablen zuweisen.

Dazu wird der sogenannte “Adress-of-Operator &” verwendet. Um unseren neuen Zeiger auf den Speicherort unserer Werttypvariablen myVar zu richten, rufen wir einfach die folgende Anweisung auf:

myPointer = &myVar;

Dies vervollständigt den im vorherigen Diagramm gezeigten Link und wird als Referenzierung bezeichnet . Aus dem gleichen Grund wird der Adressoperator (&) auch als “Referenzierungsoperator” bezeichnet.

Beispiel mit der Arduino IDE

Machen wir ein Beispiel für das, was wir bisher besprochen haben in der Arduino IDE :

void setup() {
  Serial.begin(9600);
  
  int myVar = 10;  // Initialize a variable.
  
  Serial.print("myVar's lvalue: ");
  Serial.println((long) &myVar, DEC);  // Grab myVar's lvalue
  Serial.print("myVar's rvalue: ");
  Serial.println(myVar, DEC);
  Serial.println();
  
  int *myPointer;   // Declare your pointer.
  myPointer = &myVar; //Assign myVar's memory address to pointer.
  
  Serial.print("myPointer's lvalue: ");
  Serial.println((long) &myPointer, DEC);  //myPointer's lvalue
  Serial.print("myPointer's rvalue: ");
  Serial.println((long) myPointer, DEC);  //myPointer's rvalue
}

void loop() {
}

Als Ergebnis erhalten wir auf dem Monitor folgende Ausgabe:

Monitor Ausgabe des Zeiger-Beispiel Programms

De-Referenzierung

Wir haben gerade gesehen, dass ein Zeiger auf eine Stelle im Speicher verweisen kann, indem er diesem Zeiger die Speicheradresse einer Variablen mit dem Referenzoperator (&) zuweist.

Wir können noch einen Schritt weiter gehen und den an dieser Speicheradresse gespeicherten tatsächlichen Wert erhalten, indem wir den Zeiger dereferenzieren. Dies wird auch als Indirektion bezeichnet und erfolgt über den Indirektionsoperator (*) mit Ihrem Zeiger.

*myPointer = 5; // Go to memory addressed stored in myPointer's rvalue (myVar's lvalue)
                //  and place the value 5 in that memory address.

Wir erweitern das obige Programm und erhalten folgendes Ergebnis:

void setup() {
  Serial.begin(9600);
  int myVar = 10;
  
  Serial.print("myVar's lvalue: ");
  Serial.println((long) &myVar, DEC);
  Serial.print("myVar's rvalue: ");
  Serial.println(myVar, DEC);
  Serial.println();
  
  int *myPointer;
  myPointer = &myVar;
  
  Serial.print("myPointer's lvalue: ");
  Serial.println((long) &myPointer, DEC);
  Serial.print("myPointer's rvalue: ");
  Serial.println((long) myPointer, DEC);
  Serial.println();

  *myPointer = 5;  //THIS IS OUR DEREFRENCING ADDITION.
  Serial.println("-----------------------");
  Serial.println("Updating *myPointer = 5");
  Serial.println();

  Serial.print("myPointer's lvalue: ");
  Serial.println((long) &myPointer, DEC);
  Serial.print("myPointer's rvalue: ");
  Serial.println((long) myPointer, DEC);
  Serial.println();

  Serial.print("myVar's lvalue: ");
  Serial.println((long) &myVar, DEC);
  Serial.print("myVar's rvalue: ");
  Serial.println(myVar, DEC);
  Serial.println();
}
void loop() {
}

Bildlich kann das dann so dargestellt werden:

Referenzierung und Dereferenzierung

Newton Iteration

Das nach Isaak Newton benannte Verfahren, ist in der Mathematik ein häufig verwendeter Approximations-Algorithmus zur numerischen Lösung von nichtlinearen Gleichungen.

Die grundlegende Idee dieses Verfahrens ist, die Funktion in einem Ausgangspunkt zu linearisieren, d. h. ihre Tangente zu bestimmen, und die Nullstelle der Tangente als verbesserte Näherung der Nullstelle der Funktion zu verwenden.

Die erhaltene Näherung dient dann als Ausgangspunkt für einen weiteren Verbesserungsschritt. Diese Iteration erfolgt solange, bis die Änderung in der Näherungslösung eine festgesetzte Schranke unterschritten hat.

Steuerpult fürs Hobbylabor

Beitrag befindet sich in Bearbeitung ….

Mit dem Arduino lassen sich ja sehr einfach verschiedenste Steuerungsaufgaben erledigen. Die Möglichkeit über Steckbretter und Steckbrücken, die Aufbauten schnell umzusetzen sind schon sehr praktisch. Wenn man allerdings, verschiedene Steuerungen und Anzeigeeinheiten immer wieder benötigt, habe ich mir überlegt ein Arduino-Steuerpult zu bauen. Mit dem Ziel immer wieder benötigte Module stationär in ein Gehäuse mit Netzteil zu integrieren, so dass immer wieder darauf zugegriffen werden kann.

In meinem Steuerpult sind in der ersten Ausbaustufe folgende Module enthalten:

  • Netzteil mit 12V und 5V bei ca. 2A Ausgangsleistung
  • Digitales Voltmeter
  • LCD-Anzeige, 4 Zeilen und I2C Schnittstelle
  • 2×14-Segement-Anzeigen mit I2C Schnittstelle
  • Potentiometer
  • Dreh-Encoder
  • 3-Tasten, mit Pulldowns Widerständen
  • 12er Tastenfeld
  • Thermo-Drucker
  • Bepper um Töne zu erzeugen
  • Schalt-Transistoren (in Ausbaustufe 2)
  • Motor-Regler (in Ausbaustufe 2)

Dazu wurde eine Frontplatte entworfen Mund mit einer Holzfräse von Inventables realisiert:

Frontplatten-Entwurf mit Easel

Nach dem die Frontplatte entworfen und ausgefräst war, habe ich dazu passend ein Gehäuse geplant… und mit Hilfe von MDF Platten in meiner Holzwerkstatt zusammengebaut.

Die Idee ist nun, die Anschlüsse Module an der Vorderseite über Klemmleisten heraus zu führen, um diese von einem Arduino ansteuern zu können. Die immer wieder verwendeten Leitungen wie die Strom-Versorgung werden intern fest mit dem Netzgerät verdrahtet, so dass der Arduino davon unbelastet bleibt. Einzig die gemeinsame Masseleitung wird benötigt. Das verringert den Verkabelungsaufwand bei neuen Projekten erheblich.

Am Ende sieht das Ganze dan beispielsweise so aus:

Natürlich können noch wesentlich mehr Funktionsmodule eingebaut werden. Der Phantasie sind hier keine Grenzen gesetzt.