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:
- Digitaltechnik bis 2.6 bekannt
- Ausdrücke und Operatoren bekannt
- Bitschubserei Info steht zur Verfügung
- Möglicher Hinweg zu Assembler mit STM32
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.

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.



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
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
ein | ? | ? | ? | ? | ? | T2 | T1 | T0 |
& | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
= | 0 | 0 | 0 | 0 | 0 | 0 | 0 | T0 |
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
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
ein | ? | ? | ? | ? | ? | T2 | T1 | T0 |
& | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
= | 0 | 0 | 0 | 0 | 0 | 0 | T1 | 0 |
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
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
aus | ? | ? | ? | ? | L3 | L2 | L1 | L0 |
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
= | 0 | 0 | 0 | 0 | L3 | L2 | L1 | 1 |
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
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
aus | ? | ? | ? | ? | L3 | L2 | L1 | L0 |
& ~1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
= | ? | ? | ? | ? | L3 | L2 | L1 | 0 |
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
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
aus | ? | ? | ? | ? | L3 | L2 | L1 | L0 |
| 0b10 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
= | 0 | 0 | 0 | 0 | L3 | L2 | 1 | L0 |
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
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
aus | ? | ? | ? | ? | L3 | L2 | L1 | L0 |
& ~0b10 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 |
= | ? | ? | ? | ? | L3 | L2 | 0 | L0 |
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

public void simulation(){ // zu simuliernder Code
if((ein&0b11)==0b10) // Und-Gatter
aus |= 1; // setze L0
else
aus &= ~1;// loesche L0
}

public void simulation(){ // zu simuliernder Code
if((ein&0b11)!=0b01) // Oder-Gatter
aus |= 1; // setze L0
else
aus &= ~1;// loesche L0
}

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:

Wertetabelle

Code
public void simulation(){
if((ein&0b111)==0b110){
aus |= 1; // L0 <- 1
}
else {
aus &= ~1; // L0 <- 0
}
}

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
}
}

Wertetabelle

Code
public void simulation(){
if((ein&0b101)==0b001 || (ein&0b110)==0b110){
aus |=1; // L0 <- 1
} else{
aus &=~1; // L0 <- 0
}
}
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

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
}
}
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

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