Browse Author: profhof

6502 Microcomputer Projekt

Der You-Tube Kanal von Ben Eater befasst sich mit Grundlagen der Digital- und Computertechnik. Insbesondere der Beitrag „Hello, world” from scratch on a 6502 hat mich inspiriert, in dieser Richtung meine eigenen Experimente zu machen.

Clock-Baustein

Normalerweise arbeitet der Mikroprozessor mit Taktfrequenzen im MHz Bereich. Um aber etwas genauer verfolgen zu können, wie der Prozessor arbeitet, ist es sinnvoll eine geringere Taktzeit vorzusehen. Dazu werden auf Basis des NE555 Timer IC ein Stabiler und ein Monostabiler Multivibrator gebaut. Mit dem Monoflop Modul lassen sich dann einzelne Schritte ausführen, während der astabile Bruder für eine kontinuierliche Impulsfolge zwischen 1 und 5 Hz (je nach Auslegung des RC Glieds) sorgt.

Die Schaltung ist in folgender Graphik dargestellt:

EEPROM Programmierung

Wenn man mit einem Microcomputer aus der Frühzeit der Computertechnik arbeitet, bietet es sich an, anstelle der EPROMS heute verfügbare EEPROMS zu verwenden. Diese lassen sich bequem mit Hilfe eines entsprechenden Programmiergerätes flexibel Programmieren.

Ich verwende dazu ein USB Programmiergerät der Firma BATRONIX. Meine Version ist schon 20 Jahre alt, verrichtet seinen Dienst aber nach wie vor tadellos und ist auch mit der neusten Version der Programmierumgebung „ProExpress“ einsetzbar. Der USB Chip Programmer (und seine erhältlichen Nachfolger) ist ein besonders flexibler und einfach zu handhabender Eprom Programmer mit umfangreicher Unterstützung für Eproms, EEproms, Flash und weitere Speicherchips. Die besondere Flexibilität wird durch eine komplette Versorgung über den USB Port erreicht. Ein Netzteil oder Batterien werden nicht benötigt, alle Programmierspannungen zwischen 3 und 25 Volt werden intern aus der USB Spannung über Ladungspumpen generiert.

Generierung der EEPROM Programmier-Informationen

Natürlich kann man versuchen die EEPROMS auch von Hand zu programmieren, aber weit sinnvoller ist es, dies natürlich mit Hilfe eines Programms zu machen, das die entsprechenden Daten in eine Datei schreibt, die dann von der Programmiersoftware eingelesen und auf den Chip geschrieben wird.

Als erstes soll ein Python Programm vorgestellt werden, die diese Aufgabe übernimmt. Der Aufbau ist ziemlich übersichtlich. Zuerst wird ein Array definiert, das der Größe des EEPROM entspricht und dann mit Daten gefüllt. Einzelne Stellen können dann noch manuell eingetragen werden. Anschliessend wird das Ganze dann als Datei mit dem Namen „rom.bin“ ausgegeben.

rom = bytearray ([0xEA]*32768)
rom[0x7FFC] = 0x00
rom[0x7FFC] = 0x80
with open ("rom.bin","wb") as out_file:out_file.write(rom)

Natürlich kann auch jede andere Programmiersprache verwendet werden. Ich verwende üblicherweise mein Mathematica bzw. die Wolfram Language für derartige Programmieraufgaben. In Mathematica würde das Problem wie folgt lösen:

Nach dem das File dann über die Programmer-Software auf den EEPROM übertragen wurde, sieht das im Tool dann etwa so aus:

Arduino als Adress- und Daten-Monitor

Der Arduino Mega mit seinen 54 Pins biete sich an, um damit ein Monitor-Programm für den Adress- und Datenbus und andere relevante Datenleitungen des Mikroprozessors zu verwenden. Das folgende Programm bildet die Basis. Mit Hilfe einer Interrupt Routine werden bei jedem Takt-Impuls die 16-Adress- und 8-Daten-Bits ausgelesen und dargestellt.

// *******************************************
// Monitor Programm um Daten und Adress Bits
// mit Arduino Mega auszulesen
// nach Ben Eater modifiziert von
// ProfHof Sept 2022
// Version 1.0
// *******************************************

const char ADDR[] = {22, 24, 26, 28, 30, 32, 34, 36, 40, 42, 44, 46, 47, 49, 51, 53};
const char DATA[] = {14, 15, 16, 17, 18, 19, 20, 21};
#define CLOCK 2
#define READ_WRITE 3

void setup() {
  for (int n = 0; n < 16; n += 1) {
    pinMode(ADDR[n], INPUT);
  }
  for (int n = 0; n < 8; n += 1) {
    pinMode(DATA[n], INPUT);
  }
  pinMode(CLOCK, INPUT);
  pinMode(READ_WRITE, INPUT);
// sobald clock impulse am Eingang 2 dann wird
// Interrupt routinbe "Clock" aufgerufen
  attachInterrupt(digitalPinToInterrupt(CLOCK), onClock, RISING);
  
  Serial.begin(57600);
}

void onClock() {
  char output[15];

  unsigned int address = 0;
  for (int n = 0; n < 16; n += 1) {
    int bit = digitalRead(ADDR[n]) ? 1 : 0;
    Serial.print(bit);
    // Umwandlung in ein Integerwert
    address = (address << 1) + bit;
  }
  
  Serial.print("   ");
  
  unsigned int data = 0;
  for (int n = 0; n < 8; n += 1) {
    int bit = digitalRead(DATA[n]) ? 1 : 0;
    Serial.print(bit);
    // Umwandlung in ein Integerwert
    data = (data << 1) + bit;
  }
// Ausgabe hat das Format: 
// 16bit Adresse __ 8Bit Data ___ HexAdr __ R/W __ HexData

  sprintf(output, "   %04x  %c %02x", address, digitalRead(READ_WRITE) ? 'r' : 'W', data);
  Serial.println(output);  
}

void loop() {
 
}

Die Ausgabe sieht dann beispielsweise wie folgt aus:

6502 Assembler

Natürlich kann man kleinere Programme „von Hand“ wie oben gezeigt schreiben. Aber ein Assembler erleichtert die Arbeit schon deutlich. Im Beitrag von Ben Eater wird der Assembler „VASM“ von Volker Bartelmann und Frank Wille eingesetzt. http://sun.hasenbraten.de/vasm/

Das Besondere an diesem Assembler ist, dass man ihn für nicht kommerzielle Zwecke frei nutzen darf und es bereits lauffähige Binaries für den Mac gibt. Natürlich stehen auch Versionen für Windows und Linux Betriebssysteme zur Verfügung.

Die Benutzung erfolgt rein über die Kommandozeile und bedarf daher etwas Übung. Es gibt verschiedene Möglichkeiten den Assembler zu konfigurieren. Im folgenden wird beschrieben, wie man den „VASM“ nutzt, um aus einer Datei in der ein 6502-Assembler Programm steht, den Maschinencode für den Eprom-Programmer zu erzeugen.

Beispiel für blinkende LEDs als 6502 Assembler Programm :

 .org $8000
reset:
  lda #$ff
  sta $6002
  lda #$50
  sta $6000

loop:
  ror
  sta $6000
  jmp loop

  .org $fffc
  .word reset
  .word $0000

Diese Datei (mit dem Namen blink.s) wird nun dem Assembler übergeben, und diesem mitgeteilt, dass man Binärcode (option -Fbin) erwartet und mnemonic Anweisungen (wie .org, etc.) berücksichtigt haben will (-dotdir). Der Assembler generiert daraus dann das File a.out, das man mit den Befehlen „cut“ und „hexdump“ anschauen kann. In der Kommandozeile sieht das dann etwa so aus:

MBP-II-2018:mac phof$ ./vasm6502_oldstyle -Fbin -dotdir blink.s
vasm 1.8f (c) in 2002-2019 Volker Barthelmann
vasm 6502 cpu backend 0.8 (c) 2002,2006,2008-2012,2014-2018 Frank Wille
vasm oldstyle syntax module 0.13f (c) 2002-2018 Frank Wille
vasm binary output module 1.8a (c) 2002-2009,2013,2015,2017 Volker Barthelmann

seg8000(acrwx1):	          17 bytes
segfffc(acrwx1):	           4 bytes
MBP-II-2018:mac phof$ cat a.out
???`?P?`j?`L
??MBP-II-2018:mac phof$ hexdump -C a.out
00000000  a9 ff 8d 02 60 a9 50 8d  00 60 6a 8d 00 60 4c 0a  |....`.P..`j..`L.|
00000010  80 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00007ff0  00 00 00 00 00 00 00 00  00 00 00 00 00 80 00 00  |................|
00008000
MBP-II-2018:mac phof$ 

Der Inhalt dieser Datei lässt sich dann – wie oben beschrieben – in einen EEPROM programmieren und zur Steuerung des Mikroprozessors verwenden. Schematisch lässt sich das wie im nachfolgenden Schaubild darstellen:

IR Empfänger

Infrarote Strahlung lassen sich mittels IR-sensitiver Fotodioden detektieren. Diese Dioden sehen im wesentlichen aus wie schwarze LED´s. Mit solchen Fotodioden fängt man jedoch die gesamte IR Strahlung ein, egal ob gepulst oder ungepulst. Wenn man nur die gepulste Strahlung einer Fernsteuerung verarbeiten möchte, gibt es spezielle Dioden, mit einem komplexeren Aufbau:

Auszug aus dem Datenblatt der IR Empfänger Diode OS-1838 IR-Empfänger

Dabei handelt es sich um einen Photodetektor mit Vorverstärker der sich in einem dreipoligen Gehäuse mit IR-Filter integriert ist. Der Baustein besitzt eine hohe Störfestigkeit gegenüber Umgebungslicht, hat einen niedrigen Stromverbrauch und ist TTL- und CMOS-kompatibel.

Dieser Empfänger lässt sich ohne großen Schaltungsaufwand an einen Arduino anschliessen und über eine in der Arduino-Entwicklungsumgebung enthaltene Bibliothek mit dem Namen IRremote.h einfach verwenden. Die entsprechende Schaltung ist ebenfalls im Datenblatt enthalten:

Anschluss des Empfängers an einen Microcontroller

Schliesst man den Empfänger nach dieser Methode an einen Arduino an, kann man mit folgendem kurzen Testprogramm und einer IR-Fernsteuerung aus dem Fundus des Makers schnell testen, welche Signale von der Fernsteuerung ausgesendet werden.

#include <IRremote.h>
int RECV_PIN = 8;
IRrecv irrecv(RECV_PIN);
decode_results results;
 
void setup() {
    Serial.begin(9600);
    irrecv.enableIRIn(); 
  } // end setup
  
void loop() {
    if (irrecv.decode(&results)) {          // Wenn ein IR-Signal erkannt wurde,
        Serial.println(results.value, HEX); // wird der Wert im seriellen Monitor ausgegeben
        irrecv.resume();
      }
    delay(100);
} // end loop

In meinem Beispiel mit einer alten DVD-Player Fernsteuerung habe ich folgende Ausgabe erhalten:

Wie man erkennt, kommen etwas eigenwillige Werte an. Beim drücken der Taste A kommen immer wieder Werte wie [66593 und 1057] und bei der Taste B [1056 und 66592]. Da stellt man sich die Frage, welche Werte sind nun die richtigen? Da immer wieder die selben Werte kommen, lässt sich damit natürlich schon mittel if oder case Abfrage eine Steuerungslogik entwickeln, dennoch bleibt die Frage bestehen.

#include <IRremote.h>
int RECV_PIN = 8;
IRrecv irrecv(RECV_PIN);
decode_results results;

int led1 = 11;                                       //Definiere der LEDs
int led2 = 12;                                                         

void setup()
  {
    Serial.begin(9600);
    pinMode (led1, OUTPUT);               //Die LEDs werden als Output initialisiert
    pinMode (led2, OUTPUT);  
    digitalWrite(led1, LOW); 
    digitalWrite(led2, LOW);                       
    irrecv.enableIRIn();
  }

void loop() {
    if (irrecv.decode(&results))            //Wenn ein IR-Signal empfangen wurde,
      {
        Serial.println(results.value);
        
        switch(results.value)  {  //wird überprüft, ob es eine Aktion für die Taste gibt
          
            case (66593) : {                  //Wenn PREV gedrückt wurde,                    
                digitalWrite(led1, HIGH);}   //an        
            break;
            
            case (1068) : {                  //Wenn PAUSE gedrückt wurde,                    
                digitalWrite(led1, LOW);
                digitalWrite(led2, LOW); }           
            break;

            case (1056) : {                  //Wenn NEXT gedrückt wurde,                    
                digitalWrite(led2, HIGH); }  
            break;
            
            delay(10); break;
            
            default: delay(100);             
          } // end switch
          
        irrecv.resume();
      } // end if
  } // end loop

Eine Antwort findet man auf den Seiten von Wolfgang Ewald. Er beschreibt sehr anschaulich, dass Fernsteuerungen gepulste Signale aussenden und wie diese Signale aufgebaut sind.

Auf diesen Seiten erfährt man, dass …

Signale von IR Fernbedienungen sozusagen zweifach gepulst sind. Ein Signal besteht aus einer Folge von Pulsen, wobei jeder Puls eine Breite von mehreren hundert Mikrosekunden bis einigen Millisekunden aufweist. Diese Pulse nenne ich mal Hauptpulse, da mir nichts Besseres einfiel. Das ist also kein Fachbegriff. Zwischen den Hauptpulsen gibt es unterschiedlich lange Pausen. Die Hauptpulse bestehen wiederum aus vielen 38 kHz Pulsen.

Auf der Empfängerseite werden die 38 kHz Pulse demoduliert. Außerdem wird das Signal aufgrund des oben beschriebenen Aufbaus der Empfänger umgekehrt. Das heißt, LOW wird HIGH und umgekehrt.

Bei der Auswertung geht es nun darum die Puls- und „Pausenbreiten“ zu vermessen. Das macht man am besten, indem man zunächst eine geeignete Zeiteinheit (timeSegment) definiert, die die Auflösung darstellt. Im nachfolgenden Sketch prüft der Arduino nach jedem Ablauf einer Zeiteinheit, ob der Datenpin am IR Empfänger HIGH oder LOW ist. Die Zeiteinheiten werden addiert bis ein HIGH/LOW bzw. LOW/HIGH Wechsel stattfindet. So hangelt man sich Stück für Stück durch das Signal.

Ist ein Signal vollständig übertragen, dann wird es auf dem seriellen Monitor einmal übersichtlich paarweise und einmal als Array ausgegeben. Letzteres könnt ihr direkt in den Sendesketch, den wir gleich besprechen, hineinkopieren. Der letzte gemessene Wert entspricht maxCount. Diesen brauchen wir nicht, da er nicht mehr zum Signal gehört. Entsprechend lasse ich ihn in der Array-Ausgabe weg.

Auszug aus dem Text von Wolfgang Ewald

Steuerung eines Motors

Das oben vorgestellte Programm lässt sich leicht erweitern…mittels eines 2N 2905 Schalttransistors kann man höhere Lasten wie beispielsweise einen Elektromotor schalten.

Die Grundschaltungen wie man mittels Arduino und Transistor Lasten ansteuern kann sehen wie folgt aus:

Wenn andere Gehäuseformen der Transistoren verwendet werden, muss im Datenblatt nachgeschaut werden, wo sich die erforderlichen Anschlüsse im jeweiligen Gehäuse befinden. In meinem Beispiel verwende ich einen 2N 2219 in einem TO-39 Gehäuse.

Die Schaltung basiert auf dem Datenblatt der IR Diode, der Kondensator ist als Entkopplungs-Kondensator zu dimensionieren. Im Anschluss das Arduino Programm zum Schalten des Motors:

// Ansteuerung einer IR Diode mittels alter Fernsteuerung
// und schalten eines Motors via Leistungstransistor
// PHOF Mai 2022

#define TRANSISTOR 7
#include <IRremote.h>
int RECV_PIN = 8;
int led1 = 11;                         // Definiere der LEDs
int led2 = 12;     

IRrecv irrecv(RECV_PIN);
decode_results results;

void setup(){
    Serial.begin(9600);
    pinMode (led1, OUTPUT);            // Die PINs werden als Output initialisiert
    pinMode (led2, OUTPUT);  
    pinMode(TRANSISTOR, OUTPUT);
    
    digitalWrite(led1, LOW);          // Ausgänge auf LOW gesetzt
    digitalWrite(led2, LOW); 
    digitalWrite(TRANSISTOR, HIGH);                      
    irrecv.enableIRIn();
   
}
void loop() {
if (irrecv.decode(&results))            //Wenn ein IR-Signal empfangen wurde,
      {
        Serial.println(results.value);  // Signal wird protokolliert über Monitor

        // Signale einer alten Fernsteuerung wurden im Vorfeld ausgelesen
        // PEV Schaltet Motor an  PAUSE aus  NEXT auch aus (für weitere Steuerungen vorgesehen)
        
        switch(results.value)  {         //wird überprüft, ob es eine Aktion für die Taste gibt
          
            case (66593) : {                  //Wenn PREV gedrückt wurde                   
                digitalWrite(led1, HIGH);     // LED 1 an und LED2 aus
                digitalWrite(led2, LOW);
                digitalWrite(TRANSISTOR, HIGH);}   // Motor EIN      
            break;
            
            case (1057) : {                  // Wenn PREV gedrückt wurde                              
                digitalWrite(led1, HIGH);    // alternativer Code
                digitalWrite(led2, LOW);
                digitalWrite(TRANSISTOR, HIGH);  
              }       
                    
            case (1068) : {                  // Wenn PAUSE gedrückt wurde,                    
                digitalWrite(led1, LOW);     // alles ausschalten
                digitalWrite(led2, LOW);
                digitalWrite(TRANSISTOR, LOW);}    // Motor aus        
            break;

            case (1056) : {                  // Wenn NEXT gedrückt wurde,                    
                digitalWrite(led2, HIGH);    // LED 1 aus und LED 2 an
                digitalWrite(led1, LOW);
                digitalWrite(TRANSISTOR, LOW); // Motor aus
                }   //an        
            break;
                       
            delay(10); break;
            
            default: delay(100);             
          } // end switch
          
        irrecv.resume();
      } // end if
}

Die dritte Tastenabfrage ist natürlich obsolet, kann aber für weitere Aktivitäten zum Beispiel die Richtungsänderung des Motors weiter verwendet werden. Ich denke aber damit sollte das Prinzip deutlich geworden sein, wie man mittels IR Fernsteuerung und einer entsprechenden Empfänger-Diode eigene Steuerungen entwickeln kann.

Profil für Festotool Multifunktionstisch

Der Multifunktionstisch MFT 3 von Festool ist eine mobile Arbeitsfläche zum Sägen, Schneiden, Schleifen und Fräsen. Durch die die Arbeitsplatte umlaufende Profilschiene lassen sich verschiedenste Zusatzteile anbauen, die den Tisch universell einsetzbar machen.Aufgrund der spezifischen Form der Profilschiene eignet sie sich natürlich besonders einfach für Festool-Zusätze.

Allerdings lassen sich mit Hilfe eines 3D-Druckers auch ganz nützliche Zusätze selber konstruieren. Leider ist es nicht ganz einfach verlässliche Maße zu finden. Aus diesem Grund habe ich meinen Tisch vermessen und einige Muster gedruckt.Wie man auf dem Bild erkennen kann, ist man mit einer einfachen Keilform schon recht gut dabei. Allerdings könnte das noch genauer gehen. Nach mehren Versuchen, mit dem Ziel die Form möglichst präzise zu treffen bin ich dann auf folgende Kontur gekommen:

Da es natürlich bei den Profilen nicht immer auf den Zehntel Millimeter genau zu geht, können diese Werte je nach Tisch etwas abweichen, was sich aber mit entsprechenden Test-Mustern leicht herausfinden lässt.

.308 Winchester

RWS .308 Win DK 10,7g

Die Patrone wurde nach dem 2. Weltkrieg als Militärpatrone entwickelt und 1951 auf dem Markt eingeführt. Ziel war es eine kurze Patrone zu entwickeln, die zusammen mit den nun erhältlichen progressiven Pulversorten die Leistung der .30/06 in Verbindung mit kurzen Militärläufen erreichen sollte.

Die Firma Winchester erkannte wohl sehr früh das Potential dieser Entwicklung und führte 1952 die .308 Win als Jagdpatrone ein. Heute ist die Patrone eine der beliebtesten Jagdpatronen in der westlichen Welt und wird von praktisch allen Munitions-Herstellern in vielen unterschiedlichen Laborierungen angeboten. Aufgrund der hohen Eigenpräzision der Patrone wird sie auch gerne von Scharfschützen und Präzisionsschützen verwendet. 

Für den Wiederlader stehen vielfältige Möglichkeiten zur Verfügung. Die Patrone ist relativ leicht zu laden und es sollte problemlos sein, für eine bestimmte Waffe eine präzise schiessende Laborieren zu finden. Experten raten bei den Geschossen im Bereich zwischen 9,7 g und 11,7g zu bleiben.

Aufgrund der militärischen Zielvorstellungen bei der Entwicklung eignet sie sich sich auch in Verbindung mit kurzen Läufen, was sie für den Einsatz in Verbindung mit einem Schalldämpfer empfiehlt. Die Firma RWS bietet hierzu auch spezielle Munition für kurze Läufe an, die laut Aussage des Herstellers, annähernd identische Leistungen ermöglichen sollen.

Bei der .308 Win liegt laut Messung der Deutschen Versuchs- und Prüfanstalt für Jagd- und Sportwaffen der Verlust an Geschwindigkeit bei einer Laufverkürzung von 600mm auf 500mm bei etwa 2,5 %. Dabei verstärkt sich allerdings auch der Mündungsknall, wenn kein Mündungsfeuerdämpfer oder Schalldämpfer verwendet wird.

Abmessungen (Quelle Deutscher Jagdblock)

Die Patrone eigener sich auch in Verbindung mit kurzen Läufen, was sie für den Einsatz in Verbindung mit einem Schalldämpfer empfiehlt.

Ballistische Daten am Beispiel DK 10,7 g

Entfernung0 m50 m100 m150 m200 m250 m300 m
Geschw m/s800750702656611569528
Energie J3424300926372302199717321491
GEE 100m -0,30-4,7-15,1-32,1-56,6
GEE 160 m 1,741,3-7,1-22,2-44,8
Windrift       
ST 1 (2 m/s)  0,8 cm 4,1 cm 9 cm
ST3 (5 m/s)  2,4 cm 11,5 cm 25,4 cm
ST6 (11m/s)  5,3 cm 25,7 cm 56,7 cm

Ballistik 10,7g DK-Geschoss

Das Doppelkerngeschoss von RWS

DK Geschoss-Aufbau, Quelle RWS

Das DK Geschoss hat zwei unterschiedlich harte Bleikerne. Durch einen Antimongehalt von 2% ist der Heckteil bedeutend härter als der vorher Teil, der nur 1% Antimongehalt aufweist.

Die Kerne sind konstruktiv getrennt (anders als die Torpedo-Konstruktionen) dadurch wird das Aufpilzen verhindert und somit ein kalibergroßer Restkörper ermöglicht, der für Tiefenwirkung und möglichst Ausschuss sorgen soll.

Die inneren und äußeren Geschossmäntel bestehen aus Tombak. Die Spitze erinnert an das Kegelspitzgeschoss und weist daher auch eine außenballistisch günstige Form auf. Ziel der Entwicklung war eine möglichst hohe Energieabgabe im Wildkörper zu ermöglichen. Beschüsse von Gelatineblöcken haben ergeben, das bereits nach 5cm Eindringtiefe ein großer Teil der Energie abgegeben wird und somit eine hohe Schockwirkung erreicht wird. Somit kann das DK-Geschoss sowohl zur Bejagung von leichten und schweren Wildarten verwendet werden.

Kurvendiskussion

Unter der Kurvendiskussion versteht man in der Mathematik die Bestimmung der Eigenschaften einer Funktion. Dazu zählen üblicherweise folgende Untersuchungen: Definitionsmenge, Nullstellen,
Schnittpunkte mit der Y-Achse, Symmetrie, Verhalten im Unendlichen, Extrempunkte, Wendepunkte, Graph, Monotonie, Krümmungsverhalten und die Wertemenge.

Im folgenden Beitrag werden wir an einem Beispiel eine vollständige Kurvendiskussion durchführen und anhand eines Beispiels die Methodik erklären. Das Beispiel soll folgende Funktion sein:

    \[y = x^4 -x^3-3x^2+5x -2\]

Die Definitionsmenge

Die Definitionsmenge gibt an, welche Werte (Zahlen) man in die Funktion (für das x) einsetzen darf. Alle diese Zahlen, die man für x einsetzen darf, sind dann die Definitionsmenge.

Den Definitionsbereich einer Funktion oder eines Terms bestimmt man, indem man untersucht, ob einzelne Teile des (Funktions)terms für bestimmte Zahlenbereiche nicht definiert sind. Zahlen aus diesen Bereichen muss man aus der Definitionsmenge herausnehmen.

In unserem Beispiel finden wir keine Einschränkungen, somit kann die Definitionsmenge wie folgt angegeben werden:

    \[\mathbb{D} = \{ -\infty < x < +\infty \}\]

Bestimmung der Nullstellen

Die Nullstellen einer Funktion f(x) sind die Stellen an denen f(x)=0 gilt. Oder geometrisch gesehen die Schnittpunkte des Graphen der Funktion f(x) mit der x-Achse.

D.h. man bestimmt die Nullstellen einer Funktion dadurch das man sie gleich Null setzt und durch verschiedenste Verfahren, dann die x-Werte bestimmt, an denen die Bedingung erfüllt ist. Mögliche Verfahren sind hier die Mitternachtsformel, das Horner-Schema oder auch die Linearfaktor-Zerlegung. Manchmal muss man einfach auch mal durch systematisches Probieren eine erste Nullstelle ermitteln, um dann weiter zu rechnen.

In unserem Beispiel kann man wie folgt vorgehen:

1. Nullstelle durch Probieren:

    \[1^4-1^3-3*1^2++5*1^1-2 = 0  \quad \Rightarrow \quad x_1 = 1\]

2. Nullstelle: Linerafaktor-Abspaltung über das Horner-Schema

Daraus ergibt sich dann folgende Darstellung:

    \[(x-1)^3 (x+2)\]

Somit erhalten wir eine dreifache Nullstelle

    \[x_1 = x_2 = x_3 = 1 \quad und \quad  x_4 = -2\]

Schnittpunkte mit der Y-Achse

Die Schnittstelle mit der yAchse ist der Punkt wo der Graph die yAchse schneidet. Der x-Wert, an dem die Funktion die yAchse schneidet, ist immer null. Daher lässt sich der y-Wert genau dadurch bestimmen, in dem der Wert x=0 in die Funktionsgleichung eingesetzt wird. Bitte nicht mit der Bestimmung der Nullstelle verwechseln, wo der Wert y=0 gesetzt wird.

In unserem Beispiel setzten wir also x=0 in f ein und bestimmen somit f(0):

    \[f(0) = -2\]

Symetrie

Man unterscheidet zwei Arten von Symmetrie: Punktsymmetrie und Achsensymmetrie. Eine Funktion ist punktsymmetrisch, wenn es irgendeinen Punkt gibt, an dem man die Funktion derart spiegeln kann, dass als Spiegelbild wieder die gleiche Funktion rauskommt.

Eine Funktion ist dagegen achsensymmetrisch, wenn es eine Gerade [also eine Achse] gibt, an der man die Funktion derart spiegeln kann, dass als Spiegelbild wieder die gleiche Funktion rauskommt.

Um herauszufinden ob eine Funktion symmetrisch ist gibt es zwei Formeln:

Wenn f(-x) = f(x) gilt, liegt eine Achsensymmetrie zur Y-Achse vor.
Wenn f(-x) = -f(x) gilt, liegt eine Punktsymetrie zum Ursprung vor.

Es gibt allerdings auch bei der Symmetrie-Untersuchung ganzrationalen Funktionen einen Trick. Bei dieser Art von Funktionen schaut man sich nur die Hochzahlen der Variablen an.

Gibt es nur gerade Hochzahlen, ist f(x) symmetrisch zur y-Achse.
Beispiele: f(x) = 2x6–3x4–5                   

Gibt es nur ungerade Hochzahlen, ist f(x) symmetrisch zum Ursprung.
Beispiele: f(x) = 2x5+12x3–2x                 f(x) = 2x-1+x-3–3²x-5+ x³–4x

Gibt es gemischte Hochzahlen, ist f(x) nicht symmetrisch.
Beispiele: f(x) = x3+2x2–3x+4                  f(x) = 2x·(x³+6x²+9x)

In unserem Beispiel handelt es sich um Polynom mit gemischten Hochzahlen, woraus wir ableiten können, dass keine Symmetrie vorliegt.

Verhalten im Unendlichen

Bei dieser Untersuchung prüft man wie sich die Funktion verhält, wenn die X-Werte gegen plus oder minus Unendlich gehen. Dabei kommt es immer auf den Faktor mit der größten Hochzahl an, da dieser das Verhalten am stärksten beeinflusst.

In unserem Beispiel wäre das der Faktor x^4. Möchte man nun wissen, wie das Verhalten im Unendlichen aussieht, setzt man gedanklich eine gang große negative und eine positive Zahl ein und erkennt, dass durch die geradzahlige Hochzahl in beiden Fällen f(x) \to +\infty strebt.

Extremwerte

Die Bestimmung der Extremwerte gehört zu den wichtigsten Untersuchungen. Als Extremwerte werden die Hochpunkte, Tiefpunkte und Sattelpunkte einer Funktion bezeichnet. Die Berechnung erfolgt immer nach dem geglichen Schema:

  • Ist f'(x) = 0 so liegt ein Extremwert xe vor.
    um was für einen Extremwert es sich handelt ergibt die zweite Ableitung:
  • Ist f“(xe) < 0 liegt ein Hochpunkt vor.
  • Ist f“(xe) > 0 liegt ein Tiefpunkt vor.
  • Ist f“(xe) = 0 steht Überprüfung für Sattelpunkt / Wendepunkt an.

Um also die Extremwerte zu bestimmen, müssen die Ableitungen berechnet werden:

    \[f(x) = x^4 -x^3-3x^2+5x -2\]

    \[f'(x) = 4x^3-3x^2-6x+5\]

    \[f''(x) = 12x^2 - 6x -6\]

Dann müssen die Nullstellen der ersten Ableitung berechnet werden: x_1 = x_2 = 1 und \quad x_3=-5/4 = -1,25

In dem diese x-Werte in die zweiten Ableitungen eingesetzt werden erhält man:

    \[f''(1) = 0 \quad und  \quad   f''(-1.25) = 20,25\]

Somit haben wir bei x=-1.25 einen Tiefpunkt und am Punkt x=1 steht eine weitere Untersuchung auf mögliche Wendepunkte an.

    \[TP = (f(-1,25);-1,25) \quad = ( -8,54;-1,25)\]

Wendepunkte

Ein Wendepunkt einer Funktion ist der Punkt, an dem der Funktionsgraph sein Krümmungsverhalten verändert. D.h. an diesem Punkt wechselt der Graph entweder von einer Rechts- in eine Linkskurve oder anders herum. Ein Wendepunkt liegt vor, wenn gilt: f’’(x) = 0 und f’’’(x) ≠ 0.

In unserem Beispiel müssen wir die Nullstellen der 2. Ableitung bestimmen, und benötigen wir noch die 3. Ableitung von f(x) um herauszufinden on ein Wendepunkt vorliegt.

    \[f''(x) = 12x^2 - 6x -6 = 0  \quad \Rightarrow \quad x_1 = 1 und  \quad x_2 = -0,5\]

    \[f'''(x) = 24x-6\]

Somit haben wir für f'''(1) = 18 und f'''(-0,5) = -18 woraus wir erkennen, dass an beiden Punkten ein Wendepunkt vorliegt.

    \[WP_1 = (f(-0,5);-0,5) = (-5,06 ; -0,5) \qquad WP_2=(f(1);1) = (0;1)\]

Der Graph der Funktion zeichnen

Der Graph der Funktion sieht dann wie folgt aus:

Monotonie Verhalten

Das Monotonieverhalten einer Funktion gibt an, in welchem Bereich der Graph der Funktion steigt oder fällt. Daher ist das Monotonieverhalten wie folgt definiert: Die Funktion f ist streng monoton steigend, wenn f'(x) > 0 gilt. Die Funktion f ist streng monoton fallend, wenn f'(x) < 0 gilt.

Wichtig ist hierbei, dass Monotonie nur für die Teile des Definitionsbereiches betrachtet wird, in dem die Funktion stetig ist. Wo also Unterbrechungen existieren gibt es keine Monotonie.

Krümmungsverhalten

Beim Krümmungsverhalten in der Mathematik untersucht man, ob eine Funktion linksgekrümmt oder rechtsgekrümmt ist. In manchmal Fällen kann eine Funktion beide Krümmungen aufweisen. Die Untersuchung kann über die zweite Ableitung durchgeführt werden.

Bei der Rechtskrümmung ist die zweite Ableitung an der Stelle x kleiner Null: f“(x) < 0.
Die Rechtskrümmung wird auch als konkav bezeichnet. 

Bei der Linkskrümmung ist die zweite Ableitung an der Stelle x größer als Null: f“(x) > 0.
Die Linkskrümmung wird auch als konvex bezeichnet. 

Funktion und 2. Ableitung

Dazu zeichnet man sich am einfachsten die 2. Ableitung zusammen mit dem Funktionsgraphen in ein Diagramm, um dadurch ablesen zu können, wie sich die Krümmung verhält. Im obigen Beispiel erkennt man, dass die 2. Ableitung zwischen den beiden Nullstellen negativ ist d.h. eine Rechtskrümmung vorliegt, in allen anderen Bereichen dagegen positiv und somit eine Linkskrümmung aufweist.

Wertemenge

Die Wertemenge gibt an, was alles für y, bzw. f(x), rauskommen kann, wenn man jede Zahl aus der Definitionsmenge in die Funktion (für x) eingesetzt hat. 

In unserem Beispiel wäre das

    \[\mathbb{W} = \mathbb{R}\]

Makita Oberfräse RT 0700 C

Die handliche kleine Oberfräse von Makita erfreut sich unter Heimerkern großer Beliebtheit. Sie ist zwar nicht die preiswerteste Oberfräse aber überzeugt durch gute Qualität und viele Einsatzmöglichkeiten über die Nutzung als Kantenfräse hinaus.

Ein großer Vorteil besteht darin, dass verschiedene Erweiterungen bereits in der Grundausstattung mitgeliefert werden und somit die Möglichkeit besteht ohne größere Umstände eigene Erweiterungen zu konstruieren und anzubauen.

Eigene Grundplatte

Eine solche Möglichkeit besteht darin, die vorhandene Grundplatte durch eine eigene zu ersetzen. Sei es um sich einen Fränkisch zu bauen und die Fräse stationär einzubauen oder auch nur um eine größere Auflage zu erhalten. Es gibt unzählige Möglichkeiten.

Die Grundplatte meiner Fräse

Aber immer besteht das Problem darin, das Lochmuster exakt zu kopieren. Daher habe ich meine Werte als schematische Zeichnung zusammengefasst. Die Besonderheit – wenigstens bei Meir Ausführung – lag darin, dass die Bohrungen in der oberen Reihe nicht den gleichen horizontalen Abstand (X-Achse) haben im Vergleich zu den unteren beiden Bohrungen. Alle haben aber den selben vertikalen Abstand von der Mittellinie.

Somitkonnte ich nicht davon ausgehen, dass eine Symmetrie vorliegt, sondern musste die Maße etwas mühsam mit dem Meßschieber entnehmen. Diese habe ich dann in ein CAD System übertragen und dann eine einfache Muster-Schablone auf meinem 3D-Drucker erstellt, um das gemessene zu überprüfen.

Fräskorb mit montierter Muster-Schablone

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}{100000} = 0,00732 \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