| |

1.1 🚧 SPS-Logik-Simulation

Einordnen des Unterrichtsentwurfs: Hier wird Digitaltechnik mit µC Programmierung in C/Assembler in Java mit Oberfläche betrieben. Günstige Voraussetzungen:

Perfekt passend wäre der Zeitpunkt in der Eingangsklasse bei dem in Java die Oberflächenprogrammierung behandelt wurde und in Assembler die ersten Schritte unternommen werden sollen. Letztlich soll verstanden werden, wie die µC Register direkt verwendet werden mit Assembler und C.

SPS mit Zylonenauge
SPS mit Zylonenauge

Digitale Logik mit Software simulieren (Speicher programmierbare Steuerung).
Mit einem Computerprogramm kann das Verhalten digitaler Schaltungen simuliert werden, das Ein- Ausgabeverhalten wird nachgestellt. Dabei wird in einem stetigen Zyklus der Zustand der Eingänge eingelesen verarbeitet und auf den Ausgängen ausgegeben.
Bei dieser Simulation wird bei Änderung der Tasterzustände T0..T2 eine Methode der Klasse SPS.simulation() aufgerufen, die neuen Werte Ausgänge (LED L0..L3) ermittelt.
Die Beispiele und Aufgaben wurden mit Digital erstellt.
Hinweis: Bei den später verwendeten µC Boards sind Taster verbaut, hier wird aber Schalterverhalten verwendet.
Wenn auf einen Taster gedrückt wird ist er 1 und beim Loslassen wieder 0, bei Digital und in dieser Simulation bewirkt ein Klick auf die Tx-Fläche eine Umkehrung des Zustands wie bei einem Druckschalter.

Logik-Schaltung
Einfache Logikschaltung soll simuliert werden
Funktionstabelle
Funktionstabelle
Simulation mit Java Programm

Simulations-Software laden

Mit digitalRead(..) und digitalWrite(..) arbeiten

Für den Einsteiger ist es zunächst einfacher mit Operationen wie digitalRead(T0) und digitalWrite(L0,HIGH) wie bei Arduino zu denken.

digitalRead(pin:GZ):Bool gibt den logischen Zustand des Pins pin zurück: false (in C = 0), true (in C != 0)
Z.B. digitalRead(T0) gibt den Tasterzustand von T0 zurück: true oder false

digitalWrite(pin:GZ, b:Bool) schaltet den Pin aus (b=false oder LOW) oder ein (b=true oder HIGH)
Z.B.: digitalWrite(L0,true) lässt L0 leuchten

Simple Lösung für Logikschaltung im Arduino-Style

public void simulation(){ // zu simuliernder Code
  digitalWrite(L0,digitalRead(T0) && digitalRead(T1)); // Und-Gatter
  digitalWrite(L1,digitalRead(T0) || digitalRead(T1)); // Oder-Gatter
  digitalWrite(L2,digitalRead(T2));                    // Durchleitung L2<-T2
}

µC-Realität: Pins werden in Ports (GPIO) zusammengefasst

Ein µC hat viele Pins („Beinchen“). Um da Ordnung zu bekommen werden die Anschlüsse in Register-Einheiten genannt Ports, GPIOs (General Purpose Input/Output) gruppiert und ein Pin hat eine Bit-Position in so einem Port [de.wikipedia.org/wiki/GPIO]. In der Simulation werden vereinfacht die Taster in ein und die LED-Ausgänge in aus gruppiert, wobei T0 das Bit 0 in ein ist und L2 das Bit 2 in aus. Hier der Code für die Zuordnung:

public final static int T0=0,T1=1,T2=2,L0=0,L1=1,L2=2,L3=3; // Bitpositionen
public final static boolean LOW=false,HIGH=true;            // Ode an Arduino

public boolean digitalRead(int pin){ // lese Taster an Bitpositon pin
  return (ein&1<<pin) != 0; // maskiere Bit pin
}
public void digitalWrite(int pin,boolean b){ // schalte LED an Bitposition pin
  if(b) aus |= 1<<pin;    // setze Bit pin in aus
  else  aus &= ~(1<<pin); // lösche Bit pin in aus
}
public void simulation(){ // zu simuliernder Code
  digitalWrite(L0,digitalRead(T0) && digitalRead(T1)); // Und-Gatter
  digitalWrite(L1,digitalRead(T0) || digitalRead(T1)); // Oder-Gatter
  digitalWrite(L2,digitalRead(T2));                    // Durchleitung L2<-T2
}

Bit-Voodoo, Bitschubserei

Einlesen von Port-Bits mit Maskieren

Wie funktioniert digitalRead(T0)? Hier die Auswertung:

return (ein&1<<pin) != 0 // T0 -> 0
return (ein&1<<0) != 0   // 1<<0 -> 1
return (ein & 1) != 0    // Maskieren mit & siehe Tabelle
Bit76543210
ein?????T2T1T0
&00000001
=0000000T0
Bitweises & zum Maskieren

Wenn T0 gedrückt wurde ist Bit 0 von ein = 1, das Ergebnis ist !=0 dann wird true, sonst false zurück gegeben.

Wie funktioniert digitalRead(T1)? Hier die Auswertung:

return (ein&1<<pin) != 0 // T1 -> 1
return (ein&1<<1) != 0   // 1<<1 -> 2 (0b10)
return (ein & 2) != 0    // Maskieren mit & siehe Tabelle
Bit76543210
ein?????T2T1T0
&00000010
=000000T10
Bitweises & zum Maskieren

Wenn T1 gedrückt wurde ist Bit 1 von ein = 1, das Ergebnis ist !=0 dann wird true, sonst false zurück gegeben.

Sind die Klammern notwendig?

Werden die Ausdrücke in der gewünschten Reihenfolge ausgewertet? Eine Liste der Prioritäten findet sich hier:
[mezmedia.de/technische-informatik/µc-arduino-stm32/1-1s-spickzettel/]-> Operanden und Ausführungsprioritäten

//   8    5      7     Prioritäten über die Operatoren schreiben
 ein &  1 << pin != 0  // Ausdruck entsprechend der Prioritäten klammern
 ein & ((1<< pin)!= 0) // Fehler in Java bitweises Und mit Boolean
(ein & 1 << pin) != 0  // richtige Auswertungsreihenfolge durch Klammern bestimmen
//  14  2   5       Prioritäten über die Operatoren schreiben
aus &=  ~ 1 << pin  // Ausdruck entsprechend der Prioritäten klammern
aus &=((~1) << pin) // Fehler: das Komplement wird verschoben
aus &=  ~(1 <<pin)  // richtige Auswertungsreihenfolge durch Klammern bestimmen

Setzen und Löschen von Bits mit |=, &=, ~

Was bewirkt digitalWrite(L0,true)?

aus |= 1<<pin; // L0 -> 0
aus |= 1<<0;   // 1<<0 -> 1
aus |= 1;      // |= -> aus = aus | 1
Bit76543210
aus????L3L2L1L0
| 100000001
=0000L3L2L11
Bit setzen mit |

Bit 0 von aus ist 1: L0 ist hell.

Was bewirkt digitalWrite(L0,false)?

aus &= ~(1<<pin); // L0 -> 0
aus &= ~(1<<0);   // 1<<0 -> 1
aus &= ~1;        // &= -> aus = aus & ~1
Bit76543210
aus????L3L2L1L0
& ~111111110
=????L3L2L10
Bit löschen mit & ~

Bit 0 von aus ist 0: L0 ist dunkel.

Was bewirkt digitalWrite(L1,true)?

aus |= 1<<pin; // L1 -> 1
aus |= 1<<1;   // 1<<1 -> 0b10
aus |= 0b10;   // |= -> aus = aus | 0b10
Bit76543210
aus????L3L2L1L0
| 0b1000000010
=0000L3L21L0
Bit setzen mit |

Bit 1 von aus ist 1: L1 ist hell.

Was bewirkt digitalWrite(L1,false)?

aus &= ~(1<<pin); // L1 -> 1
aus &= ~(1<<1);   // 1<<1 -> 0b10
aus &= ~0b10;     // &= -> aus = aus & ~0b10
Bit76543210
aus????L3L2L1L0
& ~0b1011111101
=????L3L20L0
Bit löschen mit & ~

Bit 1 von aus ist 0: L1 ist dunkel.

Den Code optimieren?

Compiler optimieren Code, was zur Compilezeit schon erkannt werden kann wird versucht bereits zu optimieren.
Wenn wir die Problemstellung später in Assembler umsetzen wollen wünschen wir uns möglichst wenige Zeilen.

Welcher Code werden letztlich ausgeführt? Zuerst digitalRead(..) und digitalWrite(..) auflösen:

public void simulation(){ // zu simuliernder Code
  if((ein&(1<<T0)) != 0 && (ein&(1<<T1)) != 0){
    aus |= 1<<L0;    // setze Bit L0 in aus 
  }
  else{
    aus &= ~(1<<L0); // lösche Bit L0 in aus
  }
  if((ein&(1<<T0)) != 0 || (ein&(1<<T1)) != 0){
    aus |= 1<<L1;    // setze Bit L1 in aus 
  }
  else{
    aus &= ~(1<<L1); // lösche Bit L1 in aus
  }
  if((ein&(1<<T2)) != 0){
    aus |= 1<<L2;    // setze Bit L2 in aus 
  }
  else{
    aus &= ~(1<<L2); // lösche Bit L2 in aus
  }
}

Was zur Compiler-Zeit ausgerechnet werden kann wurde aufgelöst und muss zur Laufzeit vom µC nicht mehr berechnet werden:

public void simulation(){ // zu simuliernder Code
  if((ein&0b1) != 0 && (ein&0b10) != 0){
    aus |= 1;  // setze Bit 0 in aus 
  }
  else{
    aus &= ~1; // lösche Bit 0 in aus
  }
  if((ein&0b1) != 0 || (ein&0b10) != 0){
    aus |= 0b10;  // setze Bit 1 in aus 
  }
  else{
    aus &= ~0b10; // lösche Bit 1 in aus
  }
  if((ein&0b100) != 0){
    aus |= 0b100;  // setze Bit 2 in aus 
  }
  else{
    aus &= ~0b100; // lösche Bit 2 in aus
  }
}

Noch mehr optimieren?

Und Bedingung optimiert

(ein&0b1) != 0 && (ein&0b10) != 0 // betrachte Bits einzeln
(ein&0b11) == 0b11                // betrachte Bits zusammen

Oder Bedingung optimiert

(ein&0b1) != 0 || (ein&0b10) != 0 // betrachte Bits einzeln
(ein&0b11) >= 1                   // betrachte Bits zusammen
(ein&0b11) != 0b00                // alternativ
public void simulation(){ // zu simuliernder Code
  if((ein&0b11)==0b11) // Und-Gatter
    aus |= 1;          // setze L0
  else
    aus &= ~1;         // loesche L0
  if((ein&0b11)>=1)    // Oder-Gatter
    aus |= 0b10;       // setze L1
  else
    aus &= ~0b10;      // loesche L1
  if((ein&0b100)!=0)   // wenn T2
    aus |= 0b100;      // setze L2
  else
    aus &= ~0b100;     // loesche L2
}

Beispiele für Schaltnetze

Und Gatter
And mit invertiertem Eingang
Wertetabelle
Wertetabelle
public void simulation(){ // zu simuliernder Code
  if((ein&0b11)==0b10)    // Und-Gatter
    aus |= 1; // setze L0
  else
    aus &= ~1;// loesche L0
}
Schaltung
Or mit invertiertem Eingang
Wertetabelle
Wertetabelle
public void simulation(){ // zu simuliernder Code
  if((ein&0b11)!=0b01)    // Oder-Gatter
    aus |= 1; // setze L0
  else
    aus &= ~1;// loesche L0
}
Schaltung
Xor Gatter
Wertetabelle
Wertetabelle
public void simulation(){ // zu simuliernder Code
  if((ein&0b11)==0b01 || (ein&0b11)==0b10)    // Xor-Gatter
    aus |= 1; // setze L0
  else
    aus &= ~1;// loesche L0
}

Aufgaben für Schaltnetze

Lade die Simulationssoftware, überprüfe deine Lösung für diese Digitalschaltungen:

3Fach And
3fach Und
Wertetabelle
Wertetabelle
Code
public void simulation(){
  if((ein&0b111)==0b110){
    aus |= 1; // L0 <- 1
  }
  else {
    aus &= ~1; // L0 <- 0
  }
}
Or und Exor
Oder und Exor
Wertetabelle
Wertetabelle
Code
public void simulation(){
  aus = 0;
  if((ein&0b111)!=0b001){
    aus |= 1; // L0 <- 1
  }
  if((ein&0b110)==0b100 || (ein&0b110)==0b010){
    aus |= 2; // L1 <- 1
  }
}
Multiplexer
Multiplexer
Wertetabelle
Wertetabelle
Code
public void simulation(){
  if((ein&0b101)==0b001 || (ein&0b110)==0b110){
    aus |=1;  // L0 <- 1
  } else{
    aus &=~1; // L0 <- 0
  }
}
Schaltung
Addierer
Wertetabelle
Wertetabelle
Code
public void simulation(){
    int n = ein&0b111;
    int a = 0;
    while(n>0){
      if(n%2==1)a++;
      n >>= 1;
    }
    aus = (aus & ~3) | a;
  }

Beispiele für Schaltwerke

SR-FlipFlop
SR-FlipFlop

Wegen der Rücksetzpriorität wird zuerst das R-Signal überprüft.

public void simulation(){
  if((ein&2)!=0){   // T1 = 1 ?
    aus &=~1;  // L0 <- 0
  } else if((ein&1)!=0){// T0 = 1 ?
    aus |=1;     // L0 <- 1
  }
}
Schaltung
D-FlipFlop

Im der lokalen Variablen merker wird der letzte Zustand von T1 gemerkt, um die steigende Flanke erkennen zu können. Die Variable ist als static definiert, damit der Inhalt nicht zwischen den Aufrufen von simulation() verlohren geht. Alternativ könnte auch eine globale Variable verwendet werden.

public void simulation(){
  if(!merker && (ein&2)!=0){//T1 st. Fl.
    if((ein&1)!=0){ // aus0 <- ein0
      aus |=1;
    }
    else {
      aus &=~1;
    }
  }
  merker = (ein&2)!=0;
}

Aufgaben für Schaltwerke

Schaltung
Aufgabe

Code
void simulation(){
  if((ein&0b11)==0b01){
    aus |= 1;
  } else {
    aus &= ~1;
  }
  if((ein&0b100)!=0){ // Ruecksetzprioritaet
    aus &= ~2;
  }
  else if((aus&1)!=0){// setzen?
    aus |= 2;
  }
}

Lösungen und weitere Aufgaben, allerdings etwas „schlampiger“ für C gibt es hier: Digitalschaltungssimulation mit Zylonenauge

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert