Browse Author: profhof

Einheiten und wie man sie in Größen umrechnen kann.

Es gibt immer wieder Situationen, wo man Einheiten umrechnen muss. Dabei sind die gängigsten Einheiten wie Meter in Zentimeter umzurechnen ja noch überschaubar, aber wie rechnet man beispielsweise die Einheit Liter pro Quadrat Sekunde in Kubikmeter pro Stunde um?.

Als erstes sollte man sich die grundlegenden physikalischen Einheiten und ihre Größen in Erinnerung rufen. Alle physikalischen Größen werden immer als Potenzprodukte der 7 Basisgrößen (Länge, Masse, Zeit, elektrische Stromstärke, Temperatur, Stoffmenge und Lichtstärke) dargestellt. Dieses Potenzprodukt bezeichnet man als Dimension der jeweiligen Größe. Sie darf nicht mit der Einheit der Größe verwechselt werden und ist unabhängig vom Maßsystem.

EinheitZeichenGrößeEinheitZeichenGröße
Dezid10^{-1}Dekada10^{1}
Zentic10^{-2}Hektoh10^{2}
Millim10^{-3}Kilok10^{3}
Microµ10^{-6}MegaM10^{6}
Nanon10^{-9}GigaG10^{9}
Pikop10^{-12}TeraT10^{12}
Femtof10^{-15}PetaP10^{15}
Attoa10^{-18}ExaE10^{18}
Zeptoz10^{-21}ZettaZ10^{21}
Yoktoy10^{-24}YotaY10^{24}

Um Größen entsprechend umrechnen oder konvertieren zu können, ist neben dem Maßsystem auch gut, wenn man bestimmte weitere Zusammenhänge kennt, die sich nicht zwingend als Potenz-Produkte ableiten lassen. Einige dieser Zusammenhänge sind in der folgenden Tabelle aufgeführt (ohne Anspruch auf Vollständigkeit:

Zeit [t]Basiseinheit ist die Sekunde [s]
60 s = 1 min (Minute)
60 min = 1 h (Stunde) = 3600 s
24 h = 1 Tag = 1440 min = 86400 s
Liter [l]Basiseinheit ist der Liter [l]
1 Liter = 1 dm^3 =10^{-3}m^3
1m^3 = 1000 l
Fläche [A]Basiseinheit ist der Quadratmeter m^2
1 Ar = 100 m^2
1 HAr = 100 Ar = 10^2 \cdot 100 m^2 = 10^4 m^2
Arbeit [W]Basiseinheit ist das Joule [J]
1 J = 1 Nm = 1 kg \cdot m^2\cdot s^{-2}
Leistung [P]Basiseinheit ist das Watt [W]
1 W = 1 kg \cdot m^2\cdot s^{-3}
Masse [m]Basiseinheit ist das Kilogramm [kg]
1 Tonne [t] = 1 000 kg

Umrechnungsbeispiele

Beispiel 1

Es sollen

(1)   \begin{equation*} 4,4 \cdot 10^{-6} \frac {l}{s^2} \quad =  \quad ?\quad \lbrack \frac{m^3}{h} \rbrack\end{equation*}

umgerechnet werden.

Schritt1: Konvertierung der Einheiten mit Basiswissen:

1 m^3 = 1000 Liter daraus folgt 1 l = \frac{1}{1000} m^3

1 h = 3600 s daraus folgt 1s = \frac{1}{3600} h

Eingesetzt ergibt sich dann:

(2)   \begin{equation*} 4,4 \cdot 10^{-6} \dfrac {\dfrac{1}{1000} m^3}{(\dfrac{1}{3600})^2 h}  \quad = \quad 4,4 \cdot 10^{-6} \dfrac {(3600)^2}{1000}}  \quad \lbrack \dfrac{m^3}{h} \rbrack\end{equation*}

Rechnet man dann weiter ergibt sich :

(3)   \begin{equation*} 4,4 \cdot 10^{-6} \dfrac {(12,96)\cdot 10^6}{10^3}}  \quad =  57,024 \cdot 10^{-3} \quad \lbrack \dfrac{m^3}{h} \rbrack \quad =0,057024 \quad \lbrack \dfrac{m^3}{h} \rbrack\end{equation*}

Beispiel 2

Wieviel sind 122 cm/min umgerechnet km/h ?

Auch hier geht man wieder so vor, dass man die gegebenen Einheiten in Bezug zu den gesuchten Einheiten setzt. Also 1km = 1000 m, 1m = 100 cm damit entspricht 1 km = 1000 * 100 cm = 10^5 cm oder 1 cm = \frac{1}{10^5} km.

Analog dazu verfährt man mit der zweiten Größe: 1h = 60 min damit entspricht 1 min = \frac{1}{60} h. Diese Werte werden nun wieder in die EInheitengleichung eingesetzt:

(4)   \begin{equation*} 122  \quad \lbrack \dfrac{cm}{min} \rbrack \quad = 122  \cdot \dfrac{\frac{1}{10^5}}{\frac{1}{60}} \quad \lbrack \dfrac{km}{h} \rbrack  \quad = 122 \cdot \dfrac{60}{10^5}  = \dfrac{7320}{10000} = 0,0732 \quad \lbrack \dfrac{km}{h} \rbrack\end{equation*}

Port-Manipulations Mechanismen

Es kann bei manchen Anwendungen vorkommen, dass die Arduino-Input/Output-Funktionen, wie analogRead() oder digitalWrite() zu langsam sind, und den Programmfluss stören. In solchen Fällen kann man auf die Hardware-Ports des ATmega 328 direkt zugreifen.

Da diese Form der Programmierung die Manipulation einzelner Bits erfordert, ist dazu die Kenntnis der wichtigsten Bit-Manipulationsoperationen in der Sprache C notwendig. Der Vorteil dieser Mühe liegt aber in sehr kompakten Programmen und vor allem erfolgt der Ablauf etwa 50mal schneller als mit den Standard Arduino-Input/Output-Funktionen.

Beim Arduino Uno mit dem ATmega 328 werden sämtliche Ein- und Ausgänge zu Ports zusammengefasst. Die komplette Pin-Belegung des ATMega328 ist in der Abbildung zu sehen. Hier erkennt man, dass die Pins nicht einzel angesteuert werden, sondern in mehrere Ports zusammengefasst sind.

Port B (digital pin 8 to 13)

Port C (analog input pins)

Port D (digital pins 0 to 7)

Es stehen bei diesem Prozessor drei Ports zur Verfügung, die mit den Buchstaben B, C und D bezeichnet werden. Port D beinhaltet am Arduino die digitalen Pins 0 bis 7, Port B die digitalen 8 bis 13 und Port C die analogen Pins 0 bis 5.

Wenn über Standardfunktionen wie z.B. digitalRead() sieht es für den Benutzer so aus, als würde er nur auf den von ihm gefragten Pin zugreifen und den Zustand entweder abfragen oder ändern. Im Hintergrund werden aber bei jeder dieser Operationen die Pins eines gesamten Ports angesprochen und dann die entsprechenden Ports so manipuliert, dass es so aussieht, als könnte man einen eInzelnen Pin adressieren. Daher benötigt ein DigitalWrite() Aufruf um einen einzelnen Pin auf HIGH oder LOW zu setzen bis zu 50 mal länger, als die direkte Port-Manipulation.

Die Ports werden von jeweils 3 Registern gesteuert, die mit DDRx, PORTx und PINx bezeichnet werden. Wobei das x für die Ports B, C und D stehen. Diese drei Register existieren für jeden Port, also zum Beispiel existiert für Port B ein DDRB-, ein PORTB- und ein PINB-Register. Analog gilt das auch für Port C und Port D. Das jeweilige DDRx-Register (DDR = Data Direction Register) legt fest, ob die PINs im entsprechenden Port als Input oder Output konfiguriert sein sollen. Das PORTx-Register legt bei der Ausgabe von Daten fest, ob die PINs HIGH oder LOW sind.

Für die Ausgabe von Daten wird das entsprechende Bit im DDRx-Register auf 1 (= Ausgabe) gesetzt. Somit kann nun die Ausgabe dadurch erfolgen, dass das korrespondierende Bit im zugeordneten PORTx- Register auf den gewünschten Wert gesetzt wird. Für die Daten-Eingabe wird im DDRx-Register das jeweilige Bit auf 0 (= Eingabe) gesetzt. Die an den Eingangs-Pins anliegenden Daten werden an die korrespondierenden Bits im PIN-Register übertragen und können dort abgerufen werden.

Beim AT Mega kommen dem selben Prinzip folgend noch zusätzliche Ports hinzu. Hier die Belegungen die ich bisher am meisten verwendet habe habe:

    //   ----    Arduino MEGA Portbelegung    ----

    //        Bit7| Bit6| Bit5| Bit4| Bit3| Bit2| Bit1| Bit0 
    // PortA:  P29 | P28 | P27 | P26 | P25 | P24 | P23 | P22 
    // PortL:  P42 | P43 | P44 | P45 | P46 | P47 | P48 | P49
    // PortF:  A7  | A6  | A5  | A4  | A3  | A2  | A1  | A0 
    // PortK:  A15 | A14 | A13 | A12 | A11 | A10 | A9  | A8
    

Aber es gibt noch weitere Ports bei AT Mega, die ich hier mal versucht habe in einer Übersicht darzustellen:

Port-Belegungen des Arduino AT Mega

Praktische Anwendung am Beispiel des Arduino Uno

Die einzelnen Register sind in der Arduino-Entwicklungsumgebung bereits als Namen vor definiert, man kann diese somit mit den entsprechenden Bezeichnungen wie beispielsweise PORTB, PINC usw. ansprechen. Beispielsweise setzt der Befehl 

DDRD = B00111010 die PINs D0, D2, D6 und D7 als Eingänge

sowie die PINs D1, D3, D4 und D5 als Ausgänge.

Wie schon erwähnt, wird über das PORTx Register fest gelegt, ob die Ausgangs-PINs auf LOW oder HIGH geschaltet werden sollen. Dabei steht das x immer symbolisch für einen der drei Ports. Um beispielsweise die PINs 8, 10 und 12 auf HIGH und PIN 9, 11 und 13 im Port B auf LOW setzen wollen, schreiben wir:

void setup(){
  DDRB = B11111111; // alle Bits als Ausgang
}
void loop (){
  PORTB = B10101010; // Wechselblinker mit allen Ausgaengen
  delay(300);
  PORTB = B01010101;
  delay(300);
}
//Weitere Beispiele:
DDRD = 0B11111111; // Alle PINs im Port D sind Output
PORTD = 0B11111111; // Alle PINs sind HIGH

Stoppuhr

Ziel ist es eine Stoppuhr zu programmieren. die Funktionsweise ist sehr gut im Beitrag auf der Internetseite von Start Hardware (https://starthardware.org/stoppuhr-mit-arduino-und-segmentanzeige-arduino-tm1637/) beschrieben:

Die Stoppuhr besitzt eine Anzeige und zwei Taster. Zuerst zeigt die Stoppuhr einfach vier Nullen an. Wird nun der Start-Taster betätigt, fängt die Anzeige an, Sekunden hochzuzählen. Drückt man den Zwischenzeit-Taster, stoppt die Zeit auf dem Display. Im Hintergrund wird aber weitergezählt. Ein erneuter Druck auf den Zwischenzeit-Taster zeigt wieder die aktuell laufende Zeit an. Drückt man den Start-Taster, stoppt die Uhr sowohl im Display, als auch im Hintergrund. Nun kann man die Uhr entweder per Druck auf den Zwischenzeit-Taster weiter laufen lassen, oder durch erneuten Druck auf den Start-Taster auf Null zurücksetzen.
(© www.starthardware.org)

Um eine derartige Funktionalität zu erzeugen verwendet man vorzugsweise eine State Maschine oder auch Zustandsautomat genannt.

State Maschine (Zustandsautomat)

Ein endlicher Automat (EA, auch Zustandsmaschine, Zustandsautomat; englisch finite state machine, FSM) ist ein Modell eines Verhaltens, bestehend aus Zuständen, Zustandsübergängen und Aktionen. Ein Automat heißt endlich, wenn die Menge der Zustände, die er annehmen kann (später S genannt), endlich ist. Ein endlicher Automat ist ein Spezialfall aus der Menge der Automaten.

Ein Zustandsautomat ist ein mathematisches Modell zur Verhaltensbeschreibung formaler Systeme . Es ist eine sogenannte abstrakte Maschine , die sich zu jeder Zeit in genau einem von endlich vielen Zuständen befinden kann. Der ZA kann als Reaktion auf Eingaben (Events) von einem Zustand in einen anderen wechseln ; der Wechsel von einem Zustand in einen anderen wird als Zustands-Übergang bezeichnet .

Damit lassen sich beliebig komplexe Steuerungsabläufe beschreiben und Relativ einfach in gängigen Programmiersprachen implementieren. Dazu bedient man sich einer sogenannten Zustandstabelle. In dieser ist für jeden Zustand beschrieben bei welchen Ereignissen welcher Folgezustand eingenommen wird.

Ziel ist es dabei immer, die eigentliche Maschine, also das was in der Zustandstabelle ausgedrückt wird, so einfach und überschaubar wie möglich zu präsentieren. Sie implementiert die Logik und definiert was die Maschine (in unserem Fall natürlich das Programm) eigentlich macht und warum sie es macht. Für unsere Stoppuhr ist folgende Zustandsmaschine aus der Spezifikation entstanden:

switch (programState) {
  case 0: // gestoppt
    // hier werden einfach vier Nullen auf dem Display angezeigt
    // wird der Start-Taster gerdückt, springe zu State 1
    break;

case 1: // gestartet
    // zeige die vergangene Zeit auf dem Display
    // Wenn der Zwischenzeit-Taster gedrückt wird, springe zu State 2
    // Wenn der Start-Taster gedrückt wird, springe zu State 3
    break;
case 2: // Zwischenzeit anzeigen
    // zeige die Zwischenzeit an
    // Wenn der Zwischenzeit-Taster gedrückt wird, springe zu State 1
    break;
case 3: // gestoppt
    // Wenn der Zwischenzeit-Taster gedrückt wird, springe zu State 1 (weiter laufen)
    // Wenn der Start-Taster gedrückt wird, springe zu State 0 (löschen)
    break;
  }

Die Umsetzung erfolgt in meinem Fall nicht mit einer 7-Segmentanzeige sondern mit Hilfe eines 2-zeiligen LCD Displays. Das entsprechende Programm sieht dann so aus:

//=====================================
// Stoppuhr
// PHOF 2021
// Uno mit LCD inspiriert vom Beitrag 
// auf www.starthardware.org
//=====================================

#define numberOfSeconds(_time_) ((_time_ / 1000) % 60)
#define numberOfMinutes(_time_) (((_time_ / 1000) / 60) % 60)

#include <Wire.h> 

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27,20,2);

int startPin = 8; // start, stop, delete
int zwischenzeitPin = 9; // pause, continue
int stateStart;
int stateZwischenzeit;
int lastStateStart;
int lastStateZwischenzeit;

int programState = 0;

unsigned long startZeit = 0;
long zwischenzeit = 0;
long zeitAngehalten = 0;

char LCD_Line1[40];

void setup(){
  Serial.begin(9600);
  lcd.init(); lcd.backlight();
  lcd.setCursor(0,0);  lcd.print("Stoppuhr");
  sprintf(LCD_Line1, "%02d:%02d", 0, 0); // Start-Anzeige
  lcd.setCursor(6,1);lcd.print(LCD_Line1);
  pinMode(zwischenzeitPin, INPUT);
  pinMode(startPin, INPUT);
 
}// end loop


void showTime(long theTime) {
  unsigned long myTime = theTime - startZeit;
  int seconds = numberOfSeconds(myTime);
  int minutes = numberOfMinutes(myTime);
  
  sprintf(LCD_Line1, "%02d:%02d", minutes, seconds);
  lcd.setCursor(6,1);lcd.print(LCD_Line1);
  Serial.print(minutes);Serial.print(" min : ");
  Serial.print(seconds); Serial.println(" sec");

}// end showTime


void loop(){
  stateStart = digitalRead(startPin);
  stateZwischenzeit = digitalRead(zwischenzeitPin);
  
  switch (programState) {
    case 0: // gestoppt
      startZeit = 0; 
      // start
      if ((stateStart == LOW) && (stateStart != lastStateStart)) {
        startZeit = millis();
        programState = 1;
      }
      break;
    case 1: // gestartet
      showTime(millis());
      // zwischenzeit
      if ((stateZwischenzeit == LOW) && (stateZwischenzeit != lastStateZwischenzeit)) {
        zwischenzeit = millis();
        programState = 2;
      }
      // stop
      if ((stateStart == LOW) && (stateStart != lastStateStart)) {
        zeitAngehalten = millis();
        programState = 3;
      }
      break;
    case 2: // zwischenzeit
      showTime(zwischenzeit);
      // zwischenzeit ausblenden
      if ((stateZwischenzeit == LOW) && (stateZwischenzeit != lastStateZwischenzeit)) {
        programState = 1;
      }
      break;
    case 3: // gestoppt
      // weiter laufen lassen
      if ((stateZwischenzeit == LOW) && (stateZwischenzeit != lastStateZwischenzeit)) {
        startZeit = startZeit + (millis() - zeitAngehalten);
        programState = 1;
      }
      // löschen
      if ((stateStart == LOW) && (stateStart != lastStateStart)) {
        programState = 0;
      }
      break;
  } // end case

  lastStateStart = stateStart;
  lastStateZwischenzeit = stateZwischenzeit;

}

Brushless Motor Ansteuerung

Ein BLDC-Motor besteht aus zwei Hauptteilen, einem Stator und einem Rotor. In dieser Darstellung ist der Rotor ein Permanentmagnet mit zwei Polen, während der Stator aus Spulen besteht, die wie im Bild unten gezeigt angeordnet sind.

Prinzip-Schaubild eines BLDC Motors (Graphiken inspiriert von howtomechatronics.com)

Funktionsweise

Die Funktionsweise ist analog zu einem klassischen Motor. Sobald Strom durch die Spule fliesst, entsteht ein Magnetfeld, dessen Pole von der Richtung des Stroms abhängt. So dass je nach der Stromrichtung an der Spule ein anziehendes oder abstossendes Magnetfeld erzeugt werden kann.

Wird nun jede Spule nacheinander so angesteuert, dass die entstehende Kraftwirkung zwischen Stator und Rotor eine Drehwirkung erzeugt haben wir einen Motor.

Um die Effizienz des Motors zu erhöhen, können wir zwei gegenüberliegende Spulen als einzelne Spule so wickeln, dass entgegengesetzte Pole zu den Rotorpolen erzeugt werden, wodurch wir eine doppelte Anziehungskraft erhalten.

Optimierungsschritte (© howtomechatronics.com)

Mit dieser Konfiguration können wir die sechs Pole am Stator mit nur drei Spulen oder Phase erzeugen. Wir können den Wirkungsgrad weiter steigern, indem wir zwei Spulen gleichzeitig bestromen. Auf diese Weise wird eine Spule den Rotor anziehen und die andere Spule abstoßen. Damit ist leicht abzulesen, dass für eine 360 Grad Drehung des Rotors sechs Schritte erforderlich sind:

Wenn wir uns die Stromwellenform ansehen, können wir feststellen, dass in jedem Intervall eine Phase mit positivem Strom, eine Phase mit negativem Strom und die dritte Phase ausgeschaltet ist. Dies ergibt die Idee, dass wir die freien Endpunkte jeder der drei Phasen miteinander verbinden und so den Strom zwischen ihnen aufteilen oder einen einzelnen Strom verwenden können, um die beiden Phasen gleichzeitig zu erregen.

Hier ist ein Beispiel. Wenn wir im obigen Beispiel Phase A mit einer Art Schalter, zum Beispiel einem MOSFET, an die positive Gleichspannung anschließen und auf der anderen Seite die Phase B mit Masse verbinden, dann fließt der Strom von VCC durch Phase A, den Sternpunkt und Phase B, an Masse. So erzeugten wir mit nur einem einzigen Stromfluss die vier verschiedenen Pole, die den Rotor in Bewegung setzen.


Bei dieser Konfiguration haben wir tatsächlich eine Sternschaltung der Motorphasen, bei der der Sternpunkt intern verbunden ist und die anderen drei Enden der Phasen aus dem Motor herauskommen, und deshalb kommen beim bürstenlosen Motor drei Drähte heraus.

Prinzipschaltbild (inspiriert von © howtomechatronics.com)


Damit der Rotor den vollen Zyklus macht, müssen wir nur die richtigen zwei MOSFETS in jedem der 6 Intervalle aktivieren und genau darum geht es bei ESCs. Ein ESC (=elektronischer Geschwindigkeitsregler) steuert die Bewegung oder Geschwindigkeit des bürstenlosen Motors, indem er die entsprechenden MOSFETs aktiviert, um das rotierende Magnetfeld zu erzeugen, damit sich der Motor dreht. Je höher die Frequenz oder je schneller der ESC die 6 Intervalle durchläuft, desto höher ist die Drehzahl des Motors.

Ansteuerung mit einem Steuergerät

Es gibt verschiedene elektronische Steuergeräte im Internet. Hier soll an einem Beispiel gezeigt werden, wie damit ein ausgedienter Elektro-Rasenmäher-Motor angesteuert werden kann.

Das mir vorliegende Gerät mit der Bezeichnung WS55-220 kommt von einem chinesischen Hersteller ohne weitere Erklärungen. Es hat mehrere Anschlüsse die wie folgt gekennzeichnet sind:

Anschlussbild 1

Daneben finden sich weitere Bilder im Internet, aus denen eine mögliche Beschattung hervorgeht:

Anschlussbild 2

Meine eigenen Untersuchungen haben nach einigem Suchen dann auch zu der Erkenntnis geführt, dass die Steuersignale sich alle auf Masse beziehen und dann zu folgendem Schaltbild geführt.

Ansteuerung über einen Arduino

Möchte man die Ansteuerung anstelle des meist mit gelieferten Potentiometer mit einem Microcontroller durchführen, so muss die Spannungsteilung des Potentiometers durch einen MOSFET Transistor übernommen werden, der über ein PWM Signal angesteuert wird.

Aus dem obigen Prinzipschaltbild geht hervor, dass vom Controller eine Steuerspannung von +10V die über das Potentiometer an den Eingang SV geführt wird. Aus diesem Grund liegt der Ansatz nahe, eine typische MOSFET Motor-Ansteuerung herzunehmen und anstelle des Motors die Anschlüsse SV und +10V zu verwenden.

Schaltbild zum Ansteuern des Motorcontrollers über ein PWM Signal

Das zugehörige Test-Programm sieht so aus:

/*
 * Test-Programm für den Arduino UNO
 * PHOF Okt 2021
 * PWM Signal 255 = Motor Aus / 0 = Motor full speed 
 * 
 */


int analogPin = 9;   // PWM output to Input Gate of Mosfet
int i;        

void setup(){ 
  Serial.begin(115200);
}

void loop(){
  for (i=255; i>0;i=i-5){
    Serial.print("Value is:");
    Serial.println(i);
    analogWrite(analogPin, i);  // PWM out from 255 to 0 for Speed Control
    delay(3000);
  } //end for
}//end loop

Pure Funktionen oder Lambda-Ausdrücke

Reine oder Pure Funktionen sind auch bekannt als anonyme Funktionen oder auch Lambda-Ausdrücke. Reine Funktionen zeichnen sich insbesondere dadurch aus, dass sie immer das gleiche Ergebnis zurückgeben, wenn sie mit den gleichen Argumenten aufgerufen werden. Dank dieser Eigenschaft, referenzielle Transparenz genannt, ist es für derartige Funktionen nicht möglich, Seiteneffekte zu besitzen. Eine reine Funktion ist also ein rechnerisches Analogon einer mathematischen Funktion.

Eine Pure Function ist demnach eine ganz normale Programmfunktion, die lediglich zwei besondere Eigenschaft hat:

  1. Für die gleichen Eingabeparameter wird stets der gleiche Rückgabewert geliefert
  2. Es existieren keine Side-Effects (Nebenwirkungen)

Durch diese beiden Merkmale ist eine Pure Function vergleichbar mit einer schlichten mathematischen Funktion y = f(x), die für denselben x-Wert immer denselben y-Wert ermittelt.

Das bedeutet in der Praxis, dass die gesamte Programmlogik einer Funktion allein mit den Daten aus den Eingabeparametern arbeitet und als Ergebnis genau ein Rückgabewert entsteht. Während des Funktionsaufrufes entstehen keinerlei Side-Effects, wie beispielsweise das Verändern des Zustandes von Variablen außerhalb der Funktion oder der Eingabeparameter, das Schreiben in die Datenbank bzw. einer Datei oder der Aufruf anderer Funktionen, die selbst Side-Effects besitzen.

Das Konzept der Pure Function lässt sich nicht nur relativ leicht erklären, sondern ermöglicht in der Praxis Software zu schreiben, die vom Entwickler einfach zu verstehen ist, da die einzelnen Funktionen untereinander keine Abhängigkeiten besitzen und für sich allein geschrieben, verstanden und getestet werden können. Dies ist ein riesiger Vorteil. 

Ein Großteil der heute geschriebenen Software folgt jedoch einem anderen Paradigma, dem objektorientierten Modell (OO-Modell), welches genau das Gegenteil propagiert. In der objektorientierten Welt werden Funktionen als Methoden bezeichnet und zusammen mit den dazugehörigen Daten von Objekten (Klassen) gekapselt. Beim Aufruf einer Methode kann diese sowohl auf die übergebenen Eingabeparameter als auch auf die Daten des Objektes selbst zugreifen. Dabei wird bewusst der Zustand eines Objektes über die entsprechenden Methoden manipuliert, es werden also die Daten im Objekt selbst verändert. Zusätzlich werden die Daten eines Objektes als Rückgabewert eines Methodenaufrufes an andere Objekte weitergereicht und unterliegen dort der weiteren Manipulation.

Beispiel von Pure Funktion in der Wolfram Language

Es gibt mehrere gleichwertige Möglichkeiten, reine Funktionen in der Wolfram Language zu schreiben. Die Idee besteht in allen Fällen darin, ein Objekt zu konstruieren, das mit entsprechenden Argumenten eine bestimmte Funktion berechnet. Wenn also beispielsweise fun eine reine Funktion ist, dann wertet fun[a] die Funktion mit dem Argument a aus.

Die Wolfram Language erlaubt die Verwendung sogenannter reiner Funktionen (pure functions). Ihr erstes Argument wird gekennzeichnet durch das „#“ Symbol und deren Ende ist mit dem „&“-Symbol gekennzeichnet.

So wie der Name einer Funktion irrelevant ist, wenn Sie nicht erneut auf die Funktion verweisen möchten, sind auch die Namen von Argumenten in einer reinen Funktion irrelevant. Die Wolfram Language ermöglicht es Ihnen, explizite Namen für die Argumente reiner Funktionen zu vermeiden und stattdessen die Argumente durch die Angabe von „Slot-Nummern“ #n zu spezifizieren. In einer reinen Funktion von Wolfram Language steht #n für das n-te Argument, das man angeben kann. # steht für das erste Argument. . #2 steht für das zweite Argument usw.

Das nachfolgende Beispiel soll dieses Vorgehen veranschaulichen. Es soll eine Funktion definiert werden, die immer 1 addiert:

(#+1)& 

Wenn diese Funktion nun als Kopf eines Ausdrucks angegeben wird, so wird die Funktion auf alle Argumente angewendet:

(#+1)&[50]  --->  51
(#+1)&[{50,60}] ---> 51,61

Das geht natürlich auch mit mehreren Argumenten, wie das nachfolgende Beispiel zeigt:

{#2, 1 + #1, #1 + #2} &[a, b]  ---> {b, 1+a, a+b}

Grundsätzlich lassen sich beliebig viele Argumente definieren. Ab einer bestimmten Anzahl wird es aber doch sehr herausfordernd, noch die Übersicht zu behalten. Reine Funktionen in der Wolfram Language können eine beliebige Anzahl von Argumenten annehmen. Sie können ## verwenden, um alle angegebenen Argumente anzusprechen, und ##n für das n-te und nachfolgende Argumente.

f[##, ##] &[x, y]  ---> f[x,y,x,y]

Reine Funktionen ermöglichen es, Funktionen anzugeben, die auf Argumente angewendet werden können, ohne explizite Namen für die Funktionen definieren zu müssen.

Weitere Beispiele…

#^2& is a short form for a pure function that squares its argument:

Map[#^2 &, a + b + c]


This applies a function that takes the first two elements from each list. By using a pure function, you avoid having to define the function separately:

Map[Take[#, 2] &, {{2, 1, 7}, {4, 1, 5}, {3, 1, 2}}]

Berechnung des Oster-Datums

Das Osterfest (lateinisch »pascha«, von hebräisch »pessach«) ist der höchste christliche Feiertag im Jahr, an welchem der Auferstehung Jesu Christi in besonderer Weise gedacht wird. Nach altem Brauch fällt Ostern immer auf den Sonntag nach dem ersten Frühjahrsvollmond (nach gregorianischem Kalender frühestens der 22. März und spätestens der 25. April), was auf dem Konzil von Nizäa im Jahre 325 endgültig festgelegt wurde.

Daher wurden immer wieder Methoden gesucht, um den genauen Tag zu bestimmen. Hier wird der Algorithmus von Carl Friedrich Gauß vorgestellt.

Methode nach C.F. Gauß

Die Berechnung des Oster-Datums erfolgt nach Regeln, die im Jahre 325 auf dem Konzil von Nicäa beschlossen wurden. Dem zu Folge das Osterfest am ersten Sonntag nach dem Frühlingsvollmond gefeiert werden soll. Fällt aber der erste Frühlingsvollmond auf einen Sonntag, so solle das Osterfest eine Woche später erfolgen. Da der Frühlingsvollmond im gregorianischen Kalender frühestens am 21. März und spätestens am 18. April eines Jahres liegen kann, schwankt der Ostertermin demnach immer zwischen dem 22. März und dem 25. April.

Der Algorithmus von C.F. Gauß besteht aus einer Abfolge von Rechenschritten die im folgenden dargestellt sind:

Ausgangspunkt ist immer eine Jahreszahl „Jahr“ für die das Oster-Datum berechnet werden soll.

Erster Schritt: Bestimmung der erforderlichen Hilfszahlen

M und N seien zwei Hilfszahlen, die sich aus folgendem Algorithmus berechnen:

M = Rest der Division von (15 - p + k - q) : 30 
N = Rest der Divison von (4 + k - q) : 7 
k = Jahreszahl ohne die letzten beiden Ziffern  
p = Ganzzahliger Anteil der Division (13 + 8k) : 25
q = Ganzzahliger Anteil der Division k : 4


Im Gregorianischen Kalender liegen die Werte für die Jahre 1900 bis 2099 immer bei M= 24 und N = 5. Über die obige Rechenformel lassen sich aber auch Werte die vor 19oo liegen bzw. solche die ab 2100 dann Gültigkeit besitzen.

Zweiter Schritt: Berechnung der weiteren Werte

a = Rest der Division Jahr : 19
b = Rest der Division Jahr : 4
c = Rest der Division Jahr : 7
d = Rest der Division (19a + M) : 30
e = Rest der Division (2b + 4c + 6d + N) : 7

Berechnung des Oster-Datums

Ostern ist dann der

Ostern = (22 + d + e)te März, sofern der Wert zwischen 1 und 31 liegt, 
sonst ist 
Ostern = (d + e -9)te April

Es gibt aber wie oben beschrieben einige Randbedingungen zusätzlich zu berücksichtigen:

Ist d = 29 und e = 6,            dann ist Ostern nicht am 26. April sondern am 19. April
Ist d = 28 und e = 6 und a > 10, dann ist Ostern nicht am 25. April sondern am 18. April

Programmtechnische Umsetzung

Damit haben wir alle Schritte zusammen, die sich C.F. Gauß überlegt hat und können nun mit Hilfe beliebiger Programmiersprachen ein Programm erstellen, mit dessen Hilfe das Oster-Datem bestimmt werden kann. In Mathematica (Wolfram Language) könnte das wie folgt berechnet werden:

M=24; Nu=5;

For[Jahr = 1950, Jahr < 1980, Jahr++, a = Mod[Jahr, 19];
 b = Mod[Jahr, 4];
 c = Mod[Jahr, 7];
 d = Mod[(19*a + M), 30];
 e = Mod[(2 b + 4 c + 6 d + Nu), 7];
 
 Ostern1 = (22 + d + e);
 Ostern2 = (d + e - 9);
 
 If[(d == 29 && e == 6), Ostern2 = 19, Ostern2 = (d + e - 9)];
 If[(d == 28 && e == 6 && a > 10), Ostern2 = 18, 
  Ostern2 = (d + e - 9)];
 
 If[(Ostern1 < 1 || Ostern1 <= 31), {Print[Jahr, " Ostern ist am ", 
    Ostern1, ". März"]; Print[]}, Print["Ostern liegt im April"]];
 
 If[(Ostern2 >= 1 ), {Print[Jahr, " Ostern ist am ", Ostern2, 
    ". April"]; Print[]}, {Print["Ostern liegt im März s.o."], 
   Print[]}]]

Das könnte man natürlich noch viel eleganter ausführen, aber für einen ersten Test des Algorithmus müsste die Umsetzung genügen:

1950 Ostern ist am 9. April

1951 Ostern ist am 25. März

1952 Ostern ist am 13. April

1953 Ostern ist am 5. April
 
1954 Ostern ist am 18. April
 
:
:
:

1976 Ostern ist am 18. April

1977 Ostern ist am 10. April

1978 Ostern ist am 26. März
 
1979 Ostern ist am 15. April

Port-Erweiterung durch Schieberegister

Ein Schieberegister ist eine logische Schaltung, die mehrstellige binäre Signale taktgesteuert aufnehmen, speichern und wieder abgeben kann. Schieberegister ermöglichen die Transformation serieller Daten in parallele Daten und umgekehrt. Sie bestehen im Grunde aus hintereinander geschalteten D- oder JK-FlipFlops. Die Takteingänge aller Stufen werden zusammengeschaltet und über einen gemeinsamen Takt versorgt.

Da ein Flip-Flop nur ein Bit speichern kann, müssen je nach Anwendung mehrere Flip-Flops zu einem Schieberegister zusammengeschaltet werden. Bei dem weit verbreiteten IC vom Typ 74HC595 beispielsweise sind 8 FlipFlops integriert, so kann man mit lediglich 3 Arduino-Pins 8 Ausgangspins ansteuern.

Der Schaltkreis hat folgende Pin-Belegung:

Funktionsweise

Die Funktionsweise eines Schieberegisters kann man sichwie in obiger Abbildung vorstellen: Ein serieller Datenstrom wird über eine Datenleitung an den Daten-Pin DS des Bausteins übertragen. Solange das ShiftClock-Pin (SHCP) auf Low steht passiert vorerst gar nichts. Erst wenn ein Pegelwechsel von LOW nach HIGH am ShiftClock-Pin erfolgt, wird der Zustand der am DS-Pin anliegt in den Zwischenspeicher geschoben.

Funktions-Schema eines Schieberegisters

Dieser Vorgang kann beliebig oft wiederholt werden, bis der Zwischenspeicher voll ist oder genügend Daten aufgenommen wurden. Da ein Schieberegister genau genommen aus zwei Registern besteht: dem Schieberegister, in welches der Zustand der einzelnen Ausgangspins seriell, also Bit für Bit gesteuert über das ShiftClock-Pin (SHCP) geschoben wird. Und das Ausgangsregister, in das der Zustand des Schieberegisters über ein weiteres Signal am sogenannten Storage Register Clock-Pin (SRCP) hin kopiert wird.

Es handelt sich somit um zwei separierte Register deren Datenströme kontrolliert Über die Pegelwechsel von LOW nach HIGH an den beiden Steuer-Pins ShiftClock-Pin (SHCP) und Storage Register Clock-Pin (SRCP) zusammen gefügt werden können.

Beispiel Programm

//------------------------------
// Schiebregister Test
//------------------------------

int shiftPin = 4; //SH_CP_PIN
int storePin = 2; //ST_CP_PIN
int dataPin = 7;  //DS_PIN
// Dieses Muster soll ausgegeben werden
int muster[8] = {1,1,1,1,1,1,0,0}; 
 
void setup() {
 pinMode(storePin, OUTPUT);
 pinMode(shiftPin, OUTPUT);
 pinMode(dataPin, OUTPUT);
 
 digitalWrite(storePin, LOW); // storePin sicherheitshalber auf LOW

 for (int i=0; i<8; i++) {
     digitalWrite(shiftPin, LOW);
     digitalWrite(dataPin, muster[i]);
     digitalWrite(shiftPin, HIGH);
    } // end for
 
// wenn alle Daten übertragen, auf Ausgabe schalten   
 digitalWrite(storePin, HIGH);
}
 
void loop () {
}

 

Mehrere Schieberegister zusammenschalten

Als Beispiel für die Möglichkeiten mehr als 8 Ausgänge zu steuern, habe ich eine Schaltung mit 3 IC 74595 entwickelt. Aufgebaut als Experimentierplattform für den Arduino und entsprechenden Anschlussmöglichkeiten auf einer Lochrasterplatine prototypisch umgesetzt.

Prototyp einer 24Bit-Port-Erweiterung mit 3 IC 74595 Bausteinen

Die Ansteuerung erfolgt wie bei einem einzelnen Schaltkreis d.h. alle Steuersignale an den Pins SHCP und SRCP werden parallel an alle ICs angeschlossen. Nur die DS-Leitungen der zweiten und folgenden Schieberegister werden kaskadiert. Dh. der Überlauf-Ausgang Q7S des ersten Schieberegisters wird mit dem DS-Eingang des 2. Registers verbunden usw. Das folgende Blockschaltbild verdeutlicht das Prinzip.

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

In Bearbeitung…

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)