Sie sind auf Seite 1von 61

DDiiggiittaalltteecchhnniikk

ffüürr EElleekkttrroonniikkeerr iimm 22 LLeehhrrjjaahhrr

von

Alexander Wenk

2006, Alexander Wenk, 5079 Zeihen

Inhaltsverzeichnis

Sequentielle Digitaltechnik - Flipflops

1

Laborversuch: SR-Flipflops in NOR und NAND Bauweise

3

Flipflop Gruppen

4

Nicht-taktgesteuerte Flipflops

4

Taktzustandgesteuerte Flipflops

4

SR-Flipflop

5

SR Flipflop mit dominierendem R-Eingang

6

D-Flipflop (delay Flipflop)

6

Master-Slave-Flipflop

7

Labor Digitaltechnik: Taktzustandsgesteuerte Flipflops

8

Taktzustandsgesteuerte SR-Flipflops

8

D-Flipflop

8

Fertige Flipflops

8

Taktflankengesteuerte Flipflops

9

Einflankengesteuerte T-Flipflops

9

Einflankengesteuerte JK-Flipflops

10

Zweiflankengesteuertes Flipflop

11

Versuche mit Flipflops

12

Der Mikroprozessor

13

Der Aufbau eines Mikrocontrollers

14

Aufbau und Funktion eines Mikroprozessors (CPU)

15

Prozessor-Architekturen

18

Die von Neumann Maschine

52

Die Harvard Maschine

53

Beispiel eines Signalprozessors

54

Speicherkenngrössen

55

Speicherkapazität

55

Speicherorganisation

55

Zugriffszeit

55

Zykluszeit

55

Speichereinsatz in der Praxis

55

Einige Rechenbeispiele

56

Schaltkreisfamilien

57

SSeeqquueennttiieellllee DDiiggiittaalltteecchhnniikk -- FFlliippffllooppss

Ausgehend von den Kenntnissen der kombinatorischen Logik können wir uns das Grundelement der Sequentiellen Logik, das Flipflop konstruieren:

Wahrheitstabelle:

S

= Set Eingang: der Ausgang Q soll nach dem Durchschalten auf 1 sein.

R

= Reset Eingang: der Ausgang Q soll nach dem Vorgang auf 0 gehen.

Q' = Zustand des Ausganges vor dem Betätigen von S oder R.

Q = neuer Zustand des Flipflops (nach Betätigung von S oder R)

S R Q' Q 0 0 0 0 0 0 1 1 0 1 0
S
R
Q'
Q
0
0
0
0
0
0
1
1
0
1
0
0
0
1
1
0
1
0
0
1
1
0
1
1
1
1
0
1
1
1
1
1

Schritte zur Generation unseres Flipflops:

Bestimmen der logischen Gleichung aus der Wahrheitstabelle

Gleichung vereinfachen oder KV-Diagramm anwenden.

Vereinfachte Form in NAND Form bringen

Es ergibt sich folgende Schaltung:

Wir haben vorher die Grundform des Flipflops in NAND-Technologie entdeckt. Gibt es ein äquivalentes Flipflop auch in NOR-Technologie?

S R Q' Q 0 0 0 0 0 0 1 1 0 1 0
S
R
Q'
Q
0
0
0
0
0
0
1
1
0
1
0
0
0
1
1
0
1
0
0
1
1
0
1
1
1
1
0
0
1
1
1
0

Fragen zu den Grund Flipflops:

Weshalb sind Flipflops in der Schaltungstechnik so wichtig?

Datenspeicher, ermöglicht Steuerabläufe, die vom vorherigen Zustand abhängen.

Wie lässt sich ein Grund Flipflop realisieren?

Aus NAND oder NOR-Toren

Was für ein Nachteil hat unser Grund Flipflop?

Wenn S=1 und R=1 kann nicht vorausgesagt werden, welcher Endzustand das Flipflop annimmt, wenn die Eingänge wieder auf 0 gehen. Undefinierter Zustand

Laborversuch: SR-Flipflops in NOR und NAND Bauweise

Baue jeweils ein nicht taktgesteuertes Flipflop in NOR und NAND Bauweise auf. Stimmen die von uns getätigten theoretischen Überlegungen mit den Messergebnissen überein? Halte Deine Feststellungen in einer Wahrheitstabelle wie auch mit einem Impulsdiagramm fest.

NOR-Bauweise:

S R Q' Q 0 0 0 0 0 1 0 1 0 0 1
S
R
Q'
Q
0
0
0
0
0
1
0
1
0
0
1
1
1
0
0
1
0
1
1
1
0
1
1
1

NAND-Bauweise

S R Q' Q 0 0 0 0 0 1 0 1 0 0 1
S
R
Q'
Q
0
0
0
0
0
1
0
1
0
0
1
1
1
0
0
1
0
1
1
1
0
1
1
1
Q 0 0 0 0 0 1 0 1 0 0 1 1 1 0 0
Q 0 0 0 0 0 1 0 1 0 0 1 1 1 0 0

Flipflop Gruppen

Es gibt eine sehr grosse Anzahl verschiedener Flipflops, die je nach Anwendung zum Einsatz kommen. Um den Überblick nicht zu verlieren, teilen wir die Flipflops in Gruppen nach bestimmten anwendungstechnischen Gesichtspunkten ein. (Siehe auch die Übersicht der Flipflop Gruppen)

Nicht-taktgesteuerte Flipflops

In diese Gruppe können wir unser NOR resp. NAND Flipflop einteilen. Es ist die Urform aller Flipflops, wie wir im Verlaufe dieser Aufzählung sehen werden.

Schaltzeichen:

Wahrheitstabelle und Impulsdiagramm:

S R Q 0 0 Q 0 1 0 1 0 1 1 1 X
S
R
Q
0
0
Q
0
1
0
1
0
1
1
1
X

Nachteil dieses Flipflops:

0 Q 0 1 0 1 0 1 1 1 X Nachteil dieses Flipflops: Instabiler Zustand

Instabiler Zustand bei S = 1 und R = 1

Taktzustandgesteuerte Flipflops

Im Gegensatz zu den nicht taktgesteuerten Flipflops haben wir in dieser Kategorie die Möglichkeit, den Zeitpunkt der Werteübernahme von S und R durch ein Taktsignal zu steuern.

SR-Flipflop

Mit einer Modifikation unseres Grund-Flipflops erzeugen wir ein taktzustandgesteuertes SR-Flipflop:

Wahrheitstabelle und Impulsdiagramm:

T S R Q n+1 0 0 0 Q n 0 0 1 Q n
T
S
R
Q
n+1
0
0
0
Q
n
0
0
1
Q
n
0
1
0
Q
n
0
1
1
Q
n
1
0
0
Q
n
1
0
1
0
1
1
0
1
1
1
1
X
1 Q n 1 0 0 Q n 1 0 1 0 1 1 0 1

Wie aus der letzten Zeile der Wahrheitstabelle ersichtlich ist, existiert immer noch derselbe Nachteil wie beim nicht taktgesteuerten SR Flipflops.

Es existiert noch eine andere Form von Wahrheitstabelle, um das Verhalten taktgesteuerter Flipflops darzustellen, indem wir die Kolonne T herausnehmen und allgemein definieren:

t n ist ein Zeitpunkt vor einem bestimmten Taktimpuls, t n+1 ist ein Zeitpunkt nach einem bestimmten Taktimpuls.

t n t n+1 S R Q 0 0 Q n 0 1 0 1
t n
t n+1
S
R
Q
0
0
Q
n
0
1
0
1
0
1
1
1
=

SR Flipflop mit dominierendem R-Eingang

Durch eine Verriegelung gelingt es, den verbotenen Fall in unserer obigen Wahrheitstabelle zu eliminieren, wobei es hierzu verschiedene Möglichkeiten gibt.

Schaltschema:

Wahrheitstabelle und Impulsdiagramm:

t t n n+1 S R Q 0 0 Q n 0 1 0 1
t
t
n
n+1
S
R
Q
0
0
Q
n
0
1
0
1
0
1
1
1
0

D-Flipflop (delay Flipflop)

Q n 0 1 0 1 0 1 1 1 0 D-Flipflop (delay Flipflop) Das D-Flipflop

Das D-Flipflop dient dazu, den Zustand nur eines Einganges zu speichern respektive verzögert (delayed) weiterzugeben, nämlich genau dann, wenn das Taktsignal kommt.

Schaltschema und Schaltsymbol:

Wahrheitstabelle und Impulsdiagramm:

t t n n+1 D Q 0 0 1 1
t
t
n
n+1
D
Q
0
0
1
1

T

D

Q

Wahrheitstabelle und Impulsdiagramm: t t n n+1 D Q 0 0 1 1 T D Q

Master-Slave-Flipflop

Der Nachteil der taktzustandsgesteuerten Flipflops ist die Tatsache des sofortigen Durchschaltens aller Eingangsänderungen solange das Taktsignal diesen Durchschaltzustand erfüllt. Eine Möglichkeitl, dies zu verhindern ist ein Master-Slave Flipflop. Bei dieser Konfiguration nimmt der Master (=Meister) die Eingangsgrössen entgegen, wie wir das bei obigen Flipflops gesehen haben. Neu ist das Nachschalten eines Slave (=Sklaven) der das Ausgangssignal des Masters erst übernimmt, wenn der Takt wieder auf Low geht. Wir erhalten daraus ein gegen unmittelbares Durchschalten gesichertes Flipflop. Schaltung eines Master Slave D-Flipflops:

Impulsdiagramm: T D Q 1 Q
Impulsdiagramm:
T
D
Q 1
Q

Bevor wir nun zu den taktflankengesteuerten Flipflops übergehen, wollen wir jedoch die bis jetzt behandelten Grundflipflops im praktischen Versuch etwas näher kennenlernen.

Labor Digitaltechnik: Taktzustandsgesteuerte Flipflops

Bemerkung: Breche die Schaltung nicht nach jedem Versuch komplett ab, sondern behalte soviel wie möglich davon für den nächsten Versuch.

Taktzustandsgesteuerte SR-Flipflops

1. Baue aus Grundtoren (NAND oder NOR) zwei taktzustandsgesteuerte SR- Flipflops. Beachte dabei, mit möglichst wenigen Grundtoren auszukommen (Vorhanden sind NAND, NOR, Inverter, AND, OR)

2. Ergänze eines der beiden SR-Flipflops, um ein SR-Flipflop mit dominierendem R-Eingang zu erhalten. Ist der irreguläre Zustand nun beseitigt?

D-Flipflop

1. Baue ein taktzustandsgesteuertes D-Flipflop aus obigen Elementen. Was beobachtest Du bei der Änderung von D, wenn T=1? Würde folgende Beschaltung eine sinnvolle Anwendung des D-Flipflops ergeben?

2. Ergänze obiges D-Flipflop zu einem Master-Slave-Flipflop. Bringe je eine Kontrollleuchte am Ausgang des Masters und des Slaves an. Was können wir beobachten.

3. Schalte den Ausgang des Master-Slave Flipflops gemäss folgendem Prinzipschaltbild an den Eingang zurück. Was beobachten wir, wenn wir ein Taktsignal an T anlegen?

Fertige Flipflops

Sofern Du noch Zeit hast, schaue in den Datenblättern nach, was für nicht getaktete resp. taktzustandsgesteuerte Flipflops im Experimenter vorhanden sind. Teste deren Funktionsweise aus und vergleiche mit unseren selbstgebauten Flipflops.

Taktflankengesteuerte Flipflops

In den vorgängigen Betrachtungen wurden wir mit der Problematik des sofortigen Durchschaltens der Eingangszustände konfrontiert, solange T = 1 war. Mit dem Bau des Master-Slave Flipflops wurde eine Möglichkeit gefunden, diese Problematik zu lösen. Die zweite Möglichkeit ist der Einbau von Impulsgliedern, die es erlauben, nur den Momentanzustand während der Taktflanke weiterzugeben. Alle taktzustandsgesteuerten Flipflops können prinzipiell in taktflankengesteuerte umgebaut werden. Im Schaltsymbol werden Flankeneingänge wie folgt bezeichnet (Am Beispiel des D-Flipflops):

Einflankengesteuerte T-Flipflops

Aus der Möglichkeit der Flankentriggerung ergeben sich zahlreiche neue Möglichkeiten bezüglich Verarbeitung von Eingangsgrössen. Eine davon ist, den Ausgang bei jedem Taktereignis in den anderen Zustand kippen zu lassen.

Wahrheitstabelle und Impulsdiagramm:

t t n n+1 Q Q 0 1 1 0
t
t
n
n+1
Q
Q
0
1
1
0

T

Q

und Impulsdiagramm: t t n n+1 Q Q 0 1 1 0 T Q Beschaltung und

Beschaltung und Schaltsymbol

Eine weitere Variante des T-Flipflops entsteht, wenn wir einen weiteren Eingang zur Sperrung resp Freigabe des T-Flipflops verwenden.

Wahrheitstabelle und Impulsdiagramm:

t t n n+1 E Q 0 Q n 1 Q n
t
t
n
n+1
E
Q
0
Q
n
1
Q
n

T

E

Q

Impulsdiagramm: t t n n+1 E Q 0 Q n 1 Q n T E Q

Schema und Schaltsymbol:

Einflankengesteuerte JK-Flipflops

Dieses Flipflop arbeitet wie ein taktflankengesteuertes SR Flipflop. Sind jedoch beide Eingänge hoch (=verbotener Zustand bei SR-FF), so soll dieses Flipflop wie ein T-Flipflop arbeiten.

Wahrheitstabelle und Impulsdiagramm des JK Flipflops

t t n n+1 J K Q 0 0 Q n 0 1 0 1
t
t
n
n+1
J
K
Q
0
0
Q
n
0
1
0
1
0
1
1
1
Q
n

Beschaltung und Schaltsymbol

J K Q 0 0 Q n 0 1 0 1 0 1 1 1 Q

Dieser Flipfloptyp weist oft auch einen taktunabhängigen Set und Reseteingang auf und / oder hat mehrere AND verknüpfte J und K Eingänge.

Beispiel: JK Flipflop mit asynchronem Set/Reseteingang

T J K S R Q
T
J
K
S
R
Q

Zweiflankengesteuertes Flipflop

Im Gegensatz zum taktzustandsgesteuerten Master-Slave Flipflop wird beim zweiflankengesteuerten Flipflop das Eingangssignal erst weitergegeben, wenn das Taktsignal wieder seinen Ruhezustand erreicht hat. Das zweiflankenge- steuerte Flipflop ist besonders robust gegen Störeinflüsse. (Zur Erinnerung: Beim

taktzustandsgesteuerten Master-Slave Flipflop verwendeten wir diese Beschaltung, um ein Durchschalten zu vermeiden und so quasi eine erste Art einflankengesteuertes Flipflop zu erzeugen)

Beschaltung und Schaltsymbol (am Beispiel des D-Flipflops)

Impulsdiagramm:

T

D

Q 1

Q

und Schaltsymbol (am Beispiel des D-Flipflops) Impulsdiagramm: T D Q 1 Q Digitaltechnik Alexander Wenk Seite

VVeerrssuucchhee mmiitt FFlliippffllooppss

Mögliche Themen

Baue das Master-Slave Flipflop aus NAND-Toren.

Teste gängige im Sortiment enthaltene Flipflops aus.

Baue eigene Schaltungen mit Flipflops (z.B. Vorhangsteuerung)

DDeerr MMiikkrroopprroozzeessssoorr

Mikroprozessoren werden heute in sehr vielen Anwendungen eingesetzt. Wie unterscheidet sich aber der Einsatz eines Prozessors von der diskreten Schaltungstechnik? Folgendes Beispiel soll dies verdeutlichen:

Es soll im Auto ein Alarmsummer SU eingeschaltet werden wenn versucht wird, ohne angelegte Sicherheitsgurten zu fahren. Zur Steuerung sind folgende Signale vorhanden:

Sitz links benutzt

SL

Sitz rechts benutzt

SR

Gurt links geschlossen

GL

Gurt rechts geschlossen

GR

Zündschloss eingeschaltet

ZS

Schalthebel betätigt

SH

Wie sieht die Lösung dieses Problems mit Logikbausteinen aus?

Vorteil dieser Lösung: Sehr geringe Schaltzeiten, also sehr kurze Durchlaufzeit (Nur durch Laufzeit der Gatter bestimmt).

Nachteil vom Einsatz einzelner Logigkgatter:

Die Funktionalität ist fest verdrahtet, kann also nur schlecht erweitert oder verändert werden.

Um die eigentliche Funktion zwischen Ein- und Ausgängen flexibler definieren zu können, ohne bei jeder Änderung die Logikschaltung frisch zu verdrahten, wurden neue Lösungen gesucht. Eine sehr attraktive Möglichkeit ist der Einsatz eines Mikrocontrollers:

Vorteil:

Funktionalität steckt im Programm und kann deshalb leicht verändert werden.

Nachteil: Da ein Programm das Problem in sequentiellen Teilschritten löst, ist die Verarbeitungszeit wesentlich grösser als in einer direkten Hardware- Lösung.

Der Aufbau eines Mikrocontrollers

Das Herz eines Mikrocontrollers ist die Zentraleinheit CPU. Ist nur sie auf einem Chip vorhanden, sprechen wir von einem Mikroprozessor. Ist auf demselben Chip nebst der CPU noch Speicher und Ein/Ausgabe- Baugruppen integriert, handelt es sich um einen Mikrocontroller (auch Embedded Computer genannt). Mikrocontroller sind Ein-Chip (Single-Chip) Computer, d.h. sie funktionieren wie ein kompletter kleiner Computer ohne weitere Hilfsbausteine.

Folgendes Diagramm zeigt uns ein solches Mikrocontroller-System:

Diagramm zeigt uns ein solches Mikrocontroller-System: Aufbau und Funktion eines Mikroprozessors (CPU) Schauen wir

Aufbau und Funktion eines Mikroprozessors (CPU)

Schauen wir nun also das Herzstück eines Mikrocontrollers an. Mit welchen Elementen bringt es der Mikroprozessor fertig, den Informationsfluss zu koordinieren und zu verwalten? Folgendes Blockschema zeigt uns, wie eine CPU im Inneren aufgebaut sein kann.

zeigt uns, wie eine CPU im Inneren aufgebaut sein kann. Das Blockschaltbild sieht zunächst etwas verwirrend

Das Blockschaltbild sieht zunächst etwas verwirrend aus, sollte uns aber nach der Vorstellung der einzelnen Baugruppen schon nicht mehr so komplex erscheinen. Lasst uns nun die verschiedenen Bauelemente näher kennen lernen. Wir machen dies mit einer Gruppenarbeit. Die notwendigen Informationen sind im Vogel Fachbuch Band 5, Mikroprozessortechnik zu finden. Ziele dieser Gruppenarbeit:

Kurzvortrag über das bearbeitete Thema und Zusammenfassung (1 A4 Blatt)

Thema

Gruppe

CPU-Übersicht (S. 42)

Yannick, Andy

ALU-Rechenwerk (S. 43)

Pascal, David

Befehls- oder Steuerwerk (S. 44)

Samuel, Thomas

Adress- und Hilfsregister (S. 44 ff)

Florian, Selina

Systembus (S. 27)

Peter, Keno

Bausteine an Busleitungen (S. 28 ff)

Cedric, Benjamin, Sven

Wir werden auf einem Siemens 80C537 Mikrocontroller arbeiten, dessen CPU wiederum baugleich mit dem schon länger im Gebrauch stehenden Intel 8051 Prozessor ist. Das Blockschaltbild zeigt prinzipiell die Verknüpfung der uns nun bekannten Baugruppen wieder. Wir sehen aber auch, dass die Darstellung einiges komplexer ist als das Prinzipschaltbild von voriger Seite. Dafür beschreibt es die Möglichkeiten und Anschlüsse der entsprechenden CPU schon etwas genauer. Das Blockschaltbild ist ein wichtiges Dokument für die Programmierung. Wir können damit herausfinden, was für Verknüpfungen wir direkt machen können, und was wir nur über Umwege erzeugen können.

können, und was wir nur über Umwege erzeugen können. Was kann nun aber unser Siemens-Prozessor? Er

Was kann nun aber unser Siemens-Prozessor? Er ist ein kompletter uC, ist also prinzipiell für sich alleine lauffähig. In seinem Blockschaltbild finden wir die Prozessor-Architektur nur noch mit CPU abgekürzt.

Aufgabe: Entnehme aus dem Blockdiagramm die Information, was für Peripherie unser Mikrocontroller bereits beinhaltet,

Aufgabe: Entnehme aus dem Blockdiagramm die Information, was für Peripherie unser Mikrocontroller bereits beinhaltet, und nenne zu jedem Peripheriebaustein ein Beispiel, was wir damit alles realisieren könnten.

Erste Schritte mit dem Mikroprozessor

Heute wollen wir das Mikroprozessor-System ein erstes Mal in Betrieb nehmen und eine einfache Aufgabe realisieren. Aufgabe: Gebe auf deinem uP-Board die Schalterstellungen an den LED's aus.

Dazu müssen wir folgendes wissen: Wie im Blockdiagramm ersichtlich besitzt unser Prozessor verschiedene Ports. Sie haben die Bezeichnungen P0 bis P8. Mit dem Assembler-Befehl mov können wir direkt Daten von einem Port in einen anderen übertragen. Der Befehlszeile hat folgende Syntax:

Zeilenlabel: mov Ziel, Quelle

; Bemerkungen

Mit diesem Befehl werden die Quelldaten auf die mit Ziel bezeichnete Speicherstelle geschrieben. Wenn diese Speicherstelle der Port ist, wo die LED's angeschlossen sind, werden sie entsprechend dem Zahlencode aufleuchten. Der Befehl schreibt die Daten natürlich nur ein einziges Mal. Wollen wir die Schalterstellungen also laufend übertragen, müssen wir eine Programmschleife machen.

Bevor es aber überhaupt soweit ist, müssen wir die Entwicklungsumgebung entsprechend einrichten. Dazu findest Du auf unserem Server eine Schritt- für Schritt Information. Die Datei heisst Keil_EntwicklungsumgebungAnleitung.doc

Wenn Du die Entwicklungsumgebung installiert hast, kannst Du schon fast mit dem Testprogramm beginnen. Damit wir anstelle von Speicherbezeichnungen die Registerbezeichnungen eingeben kannst, ist am Anfang des Programms folgendes einzugeben:

$nomod51

$include(Reg517.inc)

Damit der Assembler weiss, wann das Programm zu Ende ist, musst Du am Schluss noch folgendes Statement angeben:

End

Nun wünsche ich viel Spass bei den ersten Schritten mit dem Mikroprozessor.

mov Befehle: Verschieben von Daten im Speicher

Im Einführungsbeispiel haben wir bereits Daten von einem Port zum nächsten verschoben. Wir wollen uns hier einmal genauer betrachten, was wir alles für Speicherbereiche in unserem System haben. Einige davon möchten wir hier etwas besser kennen lernen, andere werden wir erst später betrachten.

Die Speicherorganisation vom 80C537

Wie wir sicher schon herausgefunden haben, besitzt der 80C537 Mikrocontroller verschiedene Speicherbereiche. Das dargestellte Memory- Map gibt uns weitere Details über die Speicherbereiche preis. Folgende Speicherbereiche kennt unser Mikrocontroller:

der interne Speicher im Adressbereich 00

7Fh

die Special Function Registers (SFR's) im Adressbereich 80h

Die oberen Bytes vom RAM, das den gemeinsamen Adressbereich

FFh.

80h

Adressregister indiziert werden.

FFh

mit den SFR's verwendet. Sie können nur indirekt über ein

Das externe Program Memory

Das externe Data Memory.

Bei unserem System ist der externe Speicher so beschaltet, dass von

7FFFh 0000

Durch Schreiben auf eine Adresse, wo das Programm steht, wird dieses überschrieben. Erst ab der Adresse 8000h wird zwischen Programm- und Datenbereich unterschieden.

der Programm- und Datenspeicher identisch sind. Vorsicht:

unterschieden. der Programm- und Datenspeicher identisch sind. Vorsicht: Digitaltechnik Alexander Wenk Seite 19

Übung zum Speicherbereich

Zu diesen Speicherbereichen wollen wir eine Übung starten: Das abgebildete Programm verschiebt auf verschiedene Arten Daten im Speicherbereich. Lasst uns herausfinden, wohin die Daten gehen.

$nomod51

 

$include(Reg517.inc)

;

;

Autor: Alexander Wenk

;

Datum: 30.10.06

;

Funktion: Verschiebt Daten von einem Port zum nächsten

;

start:

mov p5, #55h mov 0F8h, #66h mov r1, #0F8h mov @r1, #099h mov 020h, #011h mov 021h, 020h mov dptr, #100h mov a, #077h movx @dptr, a ljmp 0A000h

; Setze p5 auf 55h ; Setze Adresse F8 auf 66h ; Setze Register r0 auf F8h ; Setze Speicherplatz adressiert mit r0 = F8 auf 99h ; Setze Adresse 20h mit Wert 11h ; ; Lade externen Datenpointer mit Adresse ; Lade Akku mit 77h ; Lade Akku in externen Speicher ; Gehe zurück zum Monitor

Extadr:

db 022h

ext2:

db 033h

End

Aufgaben:

Mache ein Projekt in Keil uVision und schreibe das Programm. Achte darauf dass auf Deinem Board die LED's an Port 5 und die Schalter an Port 4 angeschlossen sind.

Was müssen die LED's nach diesem Programm eigentlich anzeigen? Mache diese Überlegung, bevor Du das Programm laufen gelassen hast.

Lasse das Programm laufen und stoppe danach den Debugger wieder. Was zeigen die LED's an? Was folgerst Du aus dieser Anzeige?

Versuche nun den Speicher zu finden in dem die verschiedenen Daten abgelegt wurden.

Öffne dazu im Debugger das Memory-Window. Im Adress-Feld kannst Du zum Suchen der Speicherbereiche folgende Optionen angeben:

d:0xe8

Zeige den internen Datenspeicher ab Adresse e8h

i:0x00

Zeige den indirekt adressierbaren internen Datenspeicher

c:0x00

Zeige den externen Programmspeicher ab Adresse 0

x:0x00

Zeige den externen Datenspeicher ab Adresse 0

Fülle zur Beantwortung dieser Fragen die Tabelle aus

Aktion

Beschrieb der mov Funktion, Beeinflusster Speicherbereich und Folgerungen

mov p5, #55h

; Setze p5 auf 55h

 

mov 0F8h, #66h

; Setze Adresse F8 auf 66h

 

mov r1, #0F8h mov @r1, #099h r0 = F8 auf 99h

; Setze Register r0 auf F8h ; Setze Speicherplatz adressiert mit

 

mov 020h, #011h

; Setze Adresse 20h mit Wert 11h

 

mov 021h, 020h

 

mov dptr, #100h mov a, #077h movx @dptr, a

; Lade Datenpointer mit Adresse ; Lade Akku mit 77h ; Lade Akku in externen Speicher

 

Weitere Fragen:

Was bedeutet es beim Assembler, wenn vor einem Wert ein # steht?

Was lässt sich aus der LED-Anzeige schliessen, wenn wir das Ergebnis betrachten?

Warum wird die LED-Anzeige mit der Adressierung via r1 nicht verändert?

Was wird am internen Speicherplatz E8h angezeigt?

Weitere Bemerkungen zum Versuch:

Theorieblock zur Speicherbefehle-Übung

In der letztwöchigen Übung stellten wir fest, dass wir einiges noch besprechen müssen:

Einerseits ist das Hexadezimalsystem noch nicht bekannt.

Andererseits wurden wir von der Fülle der Speicherbefehle förmlich überfahren und benötigen einen kleinen Theorieblock über die mögliche Adressierung von Speicherbereichen.

Wir wollen diese zwei Teilaspekte nun angehen und in dieser Reihenfolge besprechen.

Binäre Codes und Zahlensysteme

Wir möchten die Zahlensysteme von bekanntem her kennenlernen. Vom Dezimalsystem ausgehend betrachten wir uns das Binärsystem um dann das in der Programmierungstechnik häufig eingesetzte Hexadezimalsystem kennen zu lernen.

Dezimales Zahlensystem

Wie ist unser gebräuchlichstes Zahlensystem aufgebaut?

Schauen wir uns hierzu die Zahl 135 an. Durch Aneinanderreihen der drei Stellen zu einer Gesamtzahl meinen wir eigentlich automatisch folgenden Zusammenhang:

135 = 1100 + 310 + 51 = 110 2 + 310 1 + 510 0

Diese kurze aber wichtige Erkenntnis wird uns in den Zahlensystemen wesentlich weiterhelfen, die wir im Folgenden kennenlernen.

Duales Zahlensystem

Das duale Zahlensystem wird auch Zweiersystem genannt. Wenn wir eine Zahl im Zweiersystem vor uns haben, fragen wir uns bestimmt, wie gross diese Zahl im Zehnersystem sein wird. Genau diese Umwandlungen vom resp. ins Zehnersystem muss ein Rechner auch machen, bevor er damit rechnen kann.

Schauen wir uns als Beispiel die duale Zahl 1011 an. Um duale Zahlen zu kennzeichnen hängen wir am besten ein 'b' hinten an die Zahl. Vergessen wir die Zahl als binär zu kennzeichnen, könnten wir sie z.B. mit einer Dezimalzahl verwechseln. Wie rechnen wir nun 1011b in eine Dezimalzahl um? Ganz einfach, indem wir jede Stelle mit der zugehörigen Wertigkeit multiplizieren

1011b = 12 3 + 02 2 + 12 1 + 12 0 = 8 + 2 + 1 = 11

Allgemein können wir eine binäre Zahl mit Variablen für die einzelnen Bits wie folgt schreiben:

b 3 b 2 b 1 b 0 = b 3 2 3 + b 2 2 2 + b 1 2 1 + b 0 2 0

Wir können uns für die Wertigkeit jeder Ziffer theoretisch eine Tabelle erstellen, damit wir nicht jedes Mal so viel rechnen müssen. Eine andere Möglichkeit ist die obige Berechnungsformel durch schrittweises Ausklammern vom Faktor 2 noch etwas zu vereinfachen:

1011b = 12 3 + 02 2 + 12 1 + 12 0 = ((12 + 0)2 + 1)2+1 = 11

Diese Variante kann mit etwas Übung mit dem Taschenrechner sehr rasch angewendet werden und ist auch für mehr Binärstellen gültig.

Um diese Übung zu erhalten befinden sich hier gleich einige Beispiele:

Welche Dezimalzahl ergeben folgende Binärzahlen?

101b =

5

11'0010b =

50

10'0101b =

37

1010'1110b =

174

1111'1111b =

255

Nachdem wir nun die Umrechnung vom Dualsystem ins Zehnersystem beherrschen, wollen wir die Berechnung einer Binärzahl aus einer Dezimalzahl betrachten. Auch hier sind wieder zwei Varianten denkbar:

Mit einer Tabelle suchen wir jeweils die grösste Binärstelle, die in der Zahl enthalten ist und subtrahieren von dieser Zahl jeweils den Wert dieser Stelle. Mit dem Rest fahren wir nach dem gleichen Verfahren fort bis wir die ganze Dualzahl haben (Der Rest ist dann 0)

Wir formen unsere Berechnungsformel um und rechnen zyklisch:

Nehmen wir zum Beweis dieser Umrechnung das Beispiel von oben. 11 = ((12 + 0)2 + 1)2+1 Dividieren wir beide Seiten durch 2, erhalten wir:

11 / 2 = [((12 + 0)2 + 1)2+1] / 2 =

5 Rest 1 = ((12 + 0)2 + 1)Rest 1 entspricht b 0 =1

Dieser Rest entspricht der niederwertigsten Binärstelle! Fahren wir entsprechend weiter, erhalten wir

5

/ 2 = 2 Rest 1 entspricht b 1 = 1

2

/ 2 = 1 Rest 0 entspricht b 2 = 0

1

/ 2 = 0 Rest 1 entspricht b 3 = 1

Es ergibt sich die Binärzahl aus b 3 b 2 b 1 b 0 = 1011b = 11 Einige Beispiele zu dieser Umformung:

Welche Binärzahl ergibt sich aus folgenden Dezimalzahlen?

27

=

1'1011b

58

=

11'1010b

72

=

100'1000b

130

=

1000'0010b

250

=

1111'1010b

Es ist Euch sicher schon aufgefallen, dass ich bei Binärzahlen die Separatoren immer nach 4 Stellen eingefügt habe. Ich unterteile damit ein Byte in zwei gleich grosse Teile. Weshalb das sinnvoll ist, sehen wir gleich beim nächsten Zahlensystem.

Hexadezimales Zahlensystem

Fassen wir 4 Bits zu einer Zahl zusammen, so entsteht das Hexadezimalsystem. Was ist die grösste mit 4 Bits darstellbare Zahl? 1111b = 15 Das Hexadezimalsystem benötigt zur Darstellung von einer Stelle also 16 Ziffern (die 0 eingeschlossen) Da wir nicht genügend Zahlenwerte haben, verwenden wir zusätzlich die Buchstaben A bis F. Wie diese Zahlen zusammenhängen, verdeutlicht folgende Tabelle:

Dez

Bin

Hex

Dez

Bin

Hex

0

0000

0

8

1000

8

1

0001

1

9

1001

9

2

0010

2

10

1010

A

3

0011

3

11

1011

B

4

0100

4

12

1100

C

5

0101

5

13

1101

D

6

0110

6

14

1110

E

7

0111

7

15

1111

F

Da die Hexadezimalzahlen eine Zusammenfassung von jeweils 4 Bit sind, ist das Dualsystem recht einfach mit obiger Tabelle ins Hexadezimalsystem

umzurechnen 1 Byte ergibt 2 Hexadezimalstellen!

Um vom resp. ins Dezimalsystem umzurechnen verfahren wir gleich wie bei den Binärzahlen. Die Wertigkeit der Stelle ist jetzt aber 16 n (n = 0, 1, Damit wir Hexadezimalzahlen nicht mit anderen Systemen verwechseln, schreiben wir hinten an die Zahl ein 'h' also z.B. 1ACh

)

Adressierungsarten von Speicher

Wir haben gesehen, dass es verschiedene mov-Befehle gibt, die teilweise dasselbe tun. Wir möchten sie jetzt der Reihe nach betrachten, so wie sie in unserem Testprogramm vorgekommen sind:

Direkte Adressierung

Mit der direkten Adressierung schreiben wir auf eine unmittelbare Adresse. In unserem Beispiel war es Port 5, auf den der Wert 55h geladen wurde.

mov p5, #55h

; Setze p5 auf 55h

Immer wenn wir eine Konstante in einen Speicher schreiben wollen, teilen wir das dem Assembler mit, indem wir ein # vor den Wert setzen. Was genau meint eigentlich die Konstante p5? Wir haben stillschweigend die Zeilen mit den $ am Anfang des Programms eingefügt:

$nomod51

$include(Reg517.inc)

Diese veranlassen den Assembler, eine Datei einzubinden:

$include(Reg517.inc)

Damit wird eine Namendatei geladen, die alle Register beinhaltet. Eigentlich sind diese Register alles Adressen im Bereich der SFR, der Special Function Registers. Wenn wir diese Datei betrachten, finden wir heraus dass der Assembler für die Konstante p5 die Adresse 0F8h einsetzt. Dies ist auch der Grund, dass die zweite Programmzeile unseren Port 5 gleich wieder überschreibt:

mov 0F8h, #66h

; Setze Adresse F8 auf 66h

Hier können wir gleich interpretieren, was diese Adressierungsart genau ausmacht. Nehmen wir an wir haben den Adressbereich des Speichers vor uns. wir können annehmen dass dieser wie eine Tabelle angeordnet ist. Jede Adresse hat einen Speicherplatz von 1 Byte. Mit der Adresse im Mov Befehl sagen wir, in welche Zelle wir schreiben wollen. Adressen werden im Assembler ohne spezielles Vorzeichen vor der Zahl angegeben.

mov 0F8h, #66h bedeutet also, dass die Speicherzelle mit der Adresse F8h mit dem Wert 66h geladen werden soll, wie unser Bild zeigt:

mov 0F8h, #66h

geladen werden soll, wie unser Bild zeigt: mov 0F8h, #66h Adresse Wert …   F6h  

Adresse

Wert

 

F6h

 

F7h

 

F8h

66h

F9h

 

 

Indirekte Adressierung

Eine andere Art der Adressierung ist die indirekte Adressierung: Anstelle einer fixen Adresse, d.h. einer Konstante im Assemblerbefehl geben wir eine Speicherzelle an, in der die Adresse abgelegt ist, die beschrieben werden soll:

mov r1, #0F8h mov @r1, #099h

; Setze Register r0 auf F8h ; Setze Speicherplatz adressiert mit r0 = F8 auf 99h

Zur indirekten Adressierung können wir nur bestimmte Register vom Prozessor verwenden. r0 und r1 sind solche, aber auch der DPTR (Data Pointer) Der erste Befehl der dargestellten Programmsequenz lädt den Adresspointer r1 mit der Adresse F8h. Der zweite beschreibt dann die Adresse, auf die Register r1 zeigt,mit der Konstanten 99h. Damit der Assembler weiss, dass es sich um indirekte Adressierung handelt, schreiben wir das @-Zeichen vor die Registerbezeichnung. Die Abbildung zeigt, was bei der indirekten Adressierung vor sich geht:

Adresse Wert Register Wert … R0 mov r1, #0F8h F6h R1 F8h F7h F8h 99h
Adresse
Wert
Register
Wert
R0
mov r1, #0F8h
F6h
R1
F8h
F7h
F8h
99h
F9h
mov @r1, #099h

Vorteil der indirekten Adressierung ist, dass wir eine Adresse dynamisch berechnen können, bevor wir auf sie schreiben. Wenn wir z.B. einen ganzen Text im Speicher ablegen möchten, können wir z.B. ein Zeichen auf eine bestimmte Adresse schreiben, dann den Adresszeiger (auch Adresspointer genannt) um 1 erhöhen und dann das nächste Zeichen ablegen.

Kopieren von Speicherzellen.

Was machen folgende Programmzeilen?

mov 020h, #011h mov 021h, 020h

; Setze Adresse 20h mit Wert 11h ; Kopiere Inhalt Speicherplatz 20h auf Zelle 21h

Der erste Befehl ist wohl bekannt: Er kopiert den Wert 11h in die Speicherzelle 20h. Der Zweite hat keine Konstantenzuweisung. Deshalb hat diese Befehlszeile eine neue Funktion: Der Inhalt der Speicherzelle 20 h wird auf die Speicherzelle 21h kopiert. Nach dem Ausführen dieser Zeile haben also beide Speicherzellen denselben Wert. Auch hier wieder ein Prinzipbild der Funktion zum besseren Verständnis:

mov 020h, #011h mov 021h, 020h

zum besseren Verständnis: mov 020h, #011h mov 021h, 020h Adresse Wert … 19h 20h 11h 21h
Adresse Wert … 19h 20h 11h 21h 11h 22h …
Adresse
Wert
19h
20h
11h
21h
11h
22h

Mov-Befehle auf externen Speicher: movx

Auf externen Speicher kann bei unserem Prozessor nur indirekt, d.h. über Adresspointer dptr zugegriffen werden. Das Problem ist hierbei, dass der Prozessor nur 8 Bit Verarbeitungstiefe, der externe Adressbus aber 16 Bit hat. Der Prozessor wäre also gar nicht in der Lage, eine vollständige Adressierung mit nur einem Befehl vorzunehmen. Indem wir den Datenpointer zuerst laden, um danach diesen für den externen Datenzugriff zu verwenden. Dies geschieht mit folgenden Programmzeilen:

mov dptr, #100h mov a, #077h movx @dptr, a

; Lade externen Datenpointer mit Adresse ; Lade Akku mit 77h ; Lade Akku in externen Speicher

Wir können nur den Inhalt vom Akku auf externen Speicher ausgeben, da es keinen anderen Befehl für externen Speicherzugriff gibt!

Definition von Speicher

Die letzten zwei Befehle sind eigentlich gar keine. Sie sind eigentlich nur Platzhalter für Speicher. In unserem Beispiel ist an der mit Extadr benannten Adresse der Wert 22h abgelegt und an der Folgenden Adresse ext2 33h. Der Assembler organisiert selber, wo dieser Speicher reserviert wird.

Extadr:

db 022h

ext2:

db 033h

Timing auf dem Prozessor-Bus

Der Datenbereich kann sowohl beschrieben wie auch gelesen werden. Wir können aber den externen Speicher mit movx einzig mit dem DPTR (= data Pointer) vernünftig ansprechen. Dies wissen wir bereits. Aber was genau geschieht auf dem externen Bus bei einem solchen Zugriff?

Zur Analyse dieser Frage habe ich ein Testprogramm verfasst und das Ergebnis mit dem Logic Analyzer aufgezeichnet:

mov

dptr, #0300h

; Stelle den Datapointer auf 300h

mov

a, #0AAh

; Schreibe via Akku den Wert AAh auf den externen Speicher

movx

@dptr, a

Wie sieht nun diese Transaktion auf dem Systembus aus? Ich bin dieser Frage mit dem Logikanalysator nachgegangen und habe folgendes Zeitdiagramm erhalten:

mit dem Logikanalysator nachgegangen und habe folgendes Zeitdiagramm erhalten: Digitaltechnik Alexander Wenk Seite 28

Maskierung von Bits oder Logikbefehle

Der Mikroprozessor resp. seine ALU besitzt auch Logikfunktionen: Wir wollen sie mit zwei Arbeitsblättern von Dario Ferraro kennen lernen und fürs Maskieren von Bits verwenden:

kennen lernen und fürs Maskieren von Bits verwenden: ORL A,#0Fh Binäre Befehle werden benötigt • zum

ORL A,#0Fh

Binäre Befehle werden benötigt

zum gezielten Setzen mit

löschen mit

Invertieren mit

Das Bitmuster, das verwendet wird, um im Akku oder in einer RAM-Adresse Bitstellen zu setzen, löschen oder invertieren, wird als Maske bezeichnet, der ganze Vorgang als Maskierung.

7

6

5

4

3

2

1

0

Bitstelle

x

x

x

x

x

x

x

x

Akkuinhalt x:0 oder 1

0

0

0

0

1

1

1

1

Maske: 0Fh

x

x

x

x

1

1

1

1

Ergebnis im Akku

Bitstellen

 

Bitstellen

 

unverändert

gesetzt

 

ANL A,#0Fh

 

7

6

5

4

3

2

1

0

Bitstelle

x

x

x

x

 

x x

x

x

Akkuinhalt x:0 oder 1

0

0

0

0

 

1 1

1

1

Maske: 0Fh

0

0

0

0

x

x

x

x

Ergebnis im Akku

Bitstellen

 

Bitstellen

 

gelöscht

unveränd

 

ert

 

XRL A,#0Fh

7

6

5

4

3

2

1

0

Bitstelle

x

x

x

x

x

x

x

x

Akkuinhalt x:0 oder 1

0

0

0

0

1

1

1

1

Maske: 0Fh

x

x

x

x

-x

-x

-x

-x

Ergebnis im Akku

Bitstellen

 

Bitstellen

   

normal

 

invertiert

1) Im Akku befindet sich der Wert 10011100b.

o löschen Sie Bit 3

anl a, # 11110111b anl a, # 0F7h

o Setzen Sie Bit 5

orl a, # 00100000b orl a, # 20h

o Invertieren Sie Bit 1

xrl a, # 00000010b xrl a, # 02h

2)

Beim Einlesen eines Schalters sind nur die Bitposition 2,4,6 von belangen. Schreiben Sie ein Programm, das aus vom Port4 die Bits 2,4,6 in den Akku schreiben.

mov a, p4 anl a, # 01010100b anl a, # 54h

3) U,V,W sind symbolische Adressen im Bitbereich. Geben Sie die Befehle für folgende Ausdrücke an:

o

o

U

U = V W

= V W

Reservation von Speicher

Der Keil-Assembler bietet uns die Möglichkeit, Speicherplatz zu reservieren. Wir haben dies bereits im Speicherbefehle-Demoprogramm am Rande gesehen. Wir wissen bereits, dass es verschiedenen Speicherbereiche im Prozessor gibt. Wenn wir nun mit dem Assembler eine Speicherzelle reservieren weist dieser einer Textbezeichnung eine Speicherstelle zu, ähnlich wie dies bei den Port- Abkürzungen P4, P5 geschieht.

Damit der Assembler überhaupt weiss, in welchem Speicher Adressen angesiedelt werden, müssen wir ihm dies mitteilen. Denn jeder Speicherbereich beginnt wieder bei der Adresse 0. Zudem können wir nur bestimmte Assemblerbefehle für bestimmte Speicherbereiche verwenden. An Stelle vom Ausdruck Speicherbereich verwenden wir häufig die Fachbezeichnung Speichersegment.

Die Wahl von Speichersegmenten geschieht mit folgenden Befehlen

Befehl

gewählter Speicherbereich / Speichersegment

CSEG [at #Adress]

Code Segment – Programmspeicher

DSEG [at #Adress]

Interner Datenspeicher

BSEG [at #Adress]

Bit-Speicherbereich, dieser umfasst 256 Bits und ist mit den Bitverarbeitungsbefehlen ansprechbar.

ISEG [at #Adress]

Interner indirekt adressierbarer Datenspeicher

XSEG [at #Adress]

Externer Datenspeicher

Der in […] geschriebene absolute Adresswert kann auch weggelassen werden, wie wir im Beispiel unten sehen. Wie können wir mit dem Speicher arbeiten? Ganz einfach. Wir definieren am Beginn des Programms mit den Segmentbefehlen und den Speicherreservationssbefeheln den Speicher und greifen mittels Assemblerbefehlen darauf zu, indem wir die Variablennamen anstelle absoluter Speicherzellenadressen ins Programm einfügen. Damit wird automatisch das Programm lesbarer!

Wir sehen einen Auszug aus einem solchen Programm:

bseg

; Folgende Befehle spielen sich im Bit-Speicher ab

bit0:

dbit 1

bit1:

dbit 1

dseg at 30h var: ds 1

; Folgende Befehle spielen sich im internen Datenspeicher ab Adresse 30h ab

cseg

; Ab hier finden die Aktionen im Codesegment ab

start:

mov var, p4 mov a, var mov bit0, c

; Speichere die Schalterstellung in var ; a = var

Bitverarbeitungs-Befehle

Es gibt eine ganze Palette von Bitverarbeitungs-Befehlen auf unserem Prozessor. Wenn wir nicht maskieren wollen sondern nur einzelne Bits verarbeiten möchten bietet der Prozessor ein leistungsfähiges Arsenal an Bitverarbeitungsbefeheln an. Ihr habt hier eine Liste dieser Befehle:

Mnemonic

Funktion

Bytes

MZ

Flags

CLR C

Lösche das Carry-Flag.

1

1

CY

CLR badr

Lösche den Inhalt von badr.

2

1

-

SETB C

Setze das Carry-Flag.

1

1

CY

SETB badr

Setze den Inhalt von badr.

2

1

-

CPL C

Invertiere den Inhalt des C-Flag.

1

1

CY

CPL badr

Invertiere den Inhalt von badr.

2

1

-

ANL C,badr

Das Carry-Flag wird überschrieben durch das Ergebnis der UND- Verknüpfung von Carry-Bit und dem Inhalt von badr.

2

2

CY

ANL C,/badr

Das Carry-Flag wird überschrieben durch das Ergebnis der UND- Verknüpfung von Carry-Bit und dem invertierten Inhalt von badr.

2

2

CY

ORL C,badr

Das Carry-Flag wird überschrieben durch das Ergebnis der ODER-Verknüpfung von Carry-Bit und dem Inhalt von badr.

2

2

CY

ORL C,/badr

Das Carry-Flag wird überschrieben durch das Ergebnis der ODER-Verknüpfung von Carry-Bit und dem invertierten Inhalt von badr.

2

2

CY

MOV C,badr

Lade in das Carry-Bit den Inhalt von badr.

2

2

CY

MOV badr,C

Lade in die badr den Inhalt des Carry-Bit.

2

2

-

Was bedeutet badr in obiger Liste? Damit ist Bitadresse gemeint. Zu den Bitadressen ist zu erwähnen, dass nicht jeder Speicherbereich Bitadressierbar ist. Es gibt 128 allgemeine Bitspeicher im internen Datenspeicher und weitere 128 im Bereich der SFR's, also dem Akku acc oder auch P4, P5.

Wir können Bits entweder mit direkten Adresszahlen adressieren, oder durch die Angabe des entsprechenden Bytes und durch einen Punkt getrennt das zu beeinflussende Bit. Beispiele

acc.0

Bit 0 vom Akkumulator

p4.3

Bit 3 vom Port 4

Übrigens mit dem EQU Befehl können wir einer bekannten Variable eine neue zuweisen. Vielleicht ist es einfacher, wenn an P4 die Schalter hängen können wir mit

Switch EQU P4

am Anfang vom Programm diese Verknüpfung erstellen. Möchten wir dies später ändern, müssten wir nur in dieser Zeile die Verknüpfung ändern.

Wir wollen diese Erkenntnisse gleich praktisch anwenden:

In der Aufgabe 3 bei der Maskierungsübung wurden logische Verknüpfungen zum Berechnen auf dem Mikroprozessor gegeben. Nun möchten wir versuchen, diese Befehle für diese Aufgabe in ein Programm zu schreiben. Unternehmt dazu folgende Schritte:

Zur Kontrolle des Programms fertigst Du eine Wahrheitstabelle an.

Definiere im Bitspeicherbereich die Bitvariablen U, V, W

Definiere die Schalter, mit denen die Variablen verknüpft werden sollen und lese diese Schalterstellungen ein.

Wende die Bitverarbeitungsbefehle an, um die Logikverknüpfung vorzunehmen

Gebe das Resultat der Verknüpfung mit einer LED aus.

Übung Bitverarbeitung und Maskieren

In einem Einleseprogramm sollen Maskierung und Bitverarbeitung angewendet werden. Es soll folgende Funktionalität aufweisen.

Öffne ein neues Projekt und lege es in einem separaten Pfad in Deinem

Homeverzeichnis ab: Vorschlag:

\\persönlichesVerzeichnis\Mikroprozessortechnik\Bitverarbeitung

Mache einen Programmkopf, in dem Autor = Dein Name, Verfassungsdatum und eine kurze Funktionsbeschreibung enthalten sind.

Definiere die Bytevariable Input und die Bitvariablen bit0, bit1, bit2

Lade die Schalterstellungen in die Variable Input

Lade s0 in bit0, s1 in bit1, s2 in Bit2.

Maskiere Input so, dass bei der Ausgabe auf die LED's nur b0 b3

b7 sollen

aufleuchten, sofern die Schalter eingeschaltet sind. Die Bits b4 für die spätere Logikausgabe dunkel bleiben.

Mache folgende logischen Verknüpfungen:

LED.4 = b0 b1; LED.5 = b0 –b1; LED.6 = (b0 b1) b2

Springe zurück zum Programmanfang.

Weitere Übungen zur Bitmaskierung und Logikfunktionen

Versuche für diese Programme die LED's und die Schalter symbolisch zu benennen. Speichere eventuelle Zwischenresultate in Variablen ab.

Erstelle zum Testen der Programme auch ein Testkonzept. Hier machst Du am sinnvollsten eine Wahrheitstabelle in der Du Sollergebnisse und die Programmausgabe gegenüberstellst!

b5

den LED-Port ausgibt. b4 von den Schaltern soll invertiert mit LED4 dargestellt werden. Weiter soll an LED0 die Und-Verknüpfung von Schalter0 Schalter3 dargestellt werden. LED1 soll leuchten, wenn keiner der Schalter s0 s0 eingeschaltet ist. LED2 und LED3 sollen immer dunkel bleiben.

1. Schreibe ein Programm das b7

von den Schaltern einliest und direkt an

2. Erzeuge ein Programm das folgende Logikverknüpfungen ausgibt.

LED0 = S0(S1S2) LED1 = S0(S1 XOR S2) LED2 = S0S1S2 LED3 = S0(-S1-S2) Alle anderen LED's sollen dunkel sein.

3. LED0 soll leuchten, wenn von S0

sind. LED7 soll leuchten, wenn nicht alle Schalter S0 aufweisen. Alle anderen LED's sollen dunkel sein.

S3

mindestens 2 Schalter eingeschaltet

S3

dieselbe Stellung

Struktogramme und Programmstrukturen

Programmstrukturen sind die grundlegendsten Elemente in jeglicher Programmiersprache. Wir finden diese Elemente in allen Programmiersprachen wieder. Um eine Problemstellung zu studieren ist es aber einfacher in einer universellen, Programmiersprachenunabhängigen Form zu arbeiten. Da wir uns Sachen bildlich gut vorstellen können, verwenden wir dazu eine grafische Darstellung.

Der Vorteil dieser Methode liegt auf der Hand. Wir überlegen uns eine Lösung zunächst programmiersprachenunabhängig. Wir müssen uns also noch nicht darum kümmern, wie wir das Programm verfassen müssen. Wir müssen uns auch nicht damit quälen, wie wir das uP- Windows- oder Prozessrechnerprogramm schreiben, sondern haben den Kopf frei für die Lösung des eigentlichen Problems. Erst wenn wir ein Struktogramm verfasst haben, gehen wir an die Umsetzung in ein Programm.

Zurück zu den Programmstrukturen. Wir schauen uns diese gleich in der Darstellungsform des Struktogrammes an. Wir unterscheidet folgende Strukturen (Auszug aus der Dokumentation von Dario Ferraro):

Linearer Ablauf

aus der Dokumentation von Dario Ferraro): Linearer Ablauf Jede Anweisung wird in einen rechteckigen Strukturblock

Jede Anweisung wird in einen rechteckigen Strukturblock geschrieben.

Die Strukturblöcke werden nacheinander von oben nach unten durchlaufen.

Leere Strukturblöcke sind nur in Verzweigungen zulässig.

Verzweigung (Alternative)

Einfache Auswahl

zulässig. Verzweigung (Alternative) Einfache Auswahl Nur wenn die Bedingung zutreffend (wahr) ist, wird der

Nur wenn die Bedingung zutreffend (wahr) ist, wird der Anweisungsblock 1 durchlaufen. Ein Anweisungsblock kann aus einer oder mehreren Anweisungen bestehen. Trifft die Bedingung nicht zu (falsch), wird der Durchlauf ohne eine weitere Anweisung fortgeführt (Austritt unten).

Zweifache Auswahl

Zweifache Auswahl Wenn die Bedingung zutreffend (wahr) ist, wird der Anweisungsblock 1 durchlaufen. Trifft die Bedingung

Wenn die Bedingung zutreffend (wahr) ist, wird der Anweisungsblock 1 durchlaufen. Trifft die Bedingung nicht zu (falsch), wird der Anweisungsblock 2 durchlaufen. Ein Anweisungsblock kann aus einer oder mehreren Anweisungen bestehen. Austritt unten nach Abarbeitung des jeweiligen Anweisungsblocks.

Mehrfachauswahl

Abarbeitung des jeweiligen Anweisungsblocks. Mehrfachauswahl Auch "verschachtelte" Auswahl genannt, da eine

Auch "verschachtelte" Auswahl genannt, da eine weitere Bedingung folgt. Die Verschachtelung ist ebenso im Nein-Fall (noch) möglich.

Dieses Auswahlverfahren ist besonders geeignet, wenn wir zwei oder mehr separate Bedingungen prüfen müssen, um eine Entscheidung zu treffen.

Beispiel: Eine Lampe soll leuchten, wenn sie einerseits freigegeben ist und ein Helligkeitssensor meldet, dass es dunkel ist.

Fallauswahl

Helligkeitssensor meldet, dass es dunkel ist. Fallauswahl Diese Struktur ist besonders geeignet bei mehr als drei

Diese Struktur ist besonders geeignet bei mehr als drei zu prüfenden Bedingungen. Allerdings funktioniert die Fallauswahl nur, wenn wir eine einzige Variable prüfen und je nach Wert der gespeicherten Zahl eine Aktion auswählen wollen.

Der Wert von "Variable" kann mit einer bestimmten Zahl (Gleichheit) oder auch mit einem Wertebereich verglichen werden. Nach der Prüfung wird der zutreffende "Fall" mit dem zugehörigen Anweisungsblock durchlaufen.

Wiederholung (Iteration)

Zählergesteuerte Schleife

Wiederholung (Iteration) Zählergesteuerte Schleife Wiederholungsstruktur, bei der die Anzahl der Durchläufe festgelegt

Wiederholungsstruktur, bei der die Anzahl der Durchläufe festgelegt ist. Als Bedingung muss eine Zählvariable angegeben und mit einem Startwert initialisiert werden. Ebenso muss ein Endwert und die (Zähl-)Schrittweite angegeben werden. Nach jedem Durchlauf des Schleifenkörpers (Anweisungsblock 1) wird die Zählvariable um die Schrittweite inkrementiert (bzw. bei negativer Schrittweite dekrementiert) und mit dem Endwert verglichen. Ist der Endwert überschritten, wird die Schleife verlassen.

Abweisende (kopfgesteuerte) Schleife

die Schleife verlassen. Abweisende (kopfgesteuerte) Schleife Wiederholungsstruktur mit vorausgehender Bedingungsprüfung.

Wiederholungsstruktur mit vorausgehender Bedingungsprüfung. Der Schleifenkörper (Anweisungsblock 1) wird nur durchlaufen, wenn (und solange) die Bedingung zutreffend (wahr) ist.

Diese Symbolik wird auch für die Zählschleife (Anzahl der Durchläufe bekannt) benutzt.

Nicht abweisende (fussgesteuerte) Schleife

bekannt) benutzt. Nicht abweisende (fussgesteuerte) Schleife Wiederholungsstruktur mit nachfolgender Bedingungsprüfung.

Wiederholungsstruktur mit nachfolgender Bedingungsprüfung. Der Schleifenkörper (Anweisungsblock 1) wird mindestens einmal durchlaufen, auch wenn die Bedingung von Anfang an nicht zutreffend (falsch) war.

Endlos-Schleife

von Anfang an nicht zutreffend (falsch) war. Endlos-Schleife Kann allenfalls durch einen Aussprung (break) verlassen

Kann allenfalls durch einen Aussprung (break) verlassen werden. Dann verwenden wir aber eher obige Schleifendarstellung.

Aussprung

Der Aussprung (break) stellt das dar, was Nassi und Shneiderman mit den Struktogrammen eigentlich vermeiden wollten. Diesen Befehl wollen wir aber nicht verwenden, denn wir können prinzipiell alle Programme klar strukturiert realisieren.Aussprung Aufruf einer Funktion oder Prozedur Symbolik zum Aufruf eines Unterprogramms bzw. einer Prozedur oder Funktion.

Aufruf einer Funktion oder Prozedur

Symbolik zum Aufruf eines Unterprogramms bzw. einer Prozedur oder Funktion. Nach Durchlauf dieser wird genau zu der aufrufenden Stelle zurückgesprungen und der nächstfolgende Strukturblock durchlaufen.

Die Funktion oder Prozedur müssen wir in einem separaten Struktogramm darstellen, es sei denn, wir verwenden eine Bibliothek (In diesem Fall ist die genaue Funktion in einem Handbuch beschrieben, sonst ist sie unbrauchbar)

in einem Handbuch beschrieben, sonst ist sie unbrauchbar) Aufgabe zu den Struktogrammen Auf S. 34 haben

Aufgabe zu den Struktogrammen

Auf S. 34 haben wir eine Übung zur Bitmaskierung direkt in Assembler programmiert. Zeichne nun zu Aufgabe 1 oder 2 ein Struktogramm. Zeichne das Struktogramm so, dass das Programm endlos abgearbeitet wird.

Zu einem verständlichen Struktogramm gehört auch eine eindeutige Variablendeklaration. Erstelle vor dem Struktogramm die Variablendeklaration, sage darin was es für eine Variable ist (Bit, Byte) und beschreibe die Funktion (Ein- Ausgabeport, Speichervariable etc.)

Struktogramme können wir von Hand zeichnen. Wir können aber auch ein Struktogramm-Programm verwenden. Ähnlich wie ein Zeichnungsprogramm bietet das den Vorteil, dass wir bei Änderungen bereits vorhandene Struktogramme einfach umstellen können.

Ich stelle Euch ein solches Struktogramm-Programm auf unserem Server zur Verfügung.

Nun wünsche ich viel Spass bei dem Zeichnen der Struktogramme!

Sprungbefehle

Wie wir in den Struktogrammen gesehen haben, können und müssen wir in Programmen auch Entscheidungen durchführen können. Um je nach Zustand das eine zu tun oder zu lassen müssen wir einzelne Programmblöcke durchführen oder überspringen können. Dazu dienen uns die Sprungbefehle. Unser Prozessor kennt dazu folgende Maschinenbefehle:

Mnemonic

Funktion

Bytes

MZ

Flags

AJMP adr11

Setze das Programm bei adr11 innerhalb der 2 kByte-Seite fort.

2

2

-

LJMP adr16

Setze das Programm bei adr16 fort.

3

2

-

SJMP rel

Setze das Programm bei rel fort (relativ zum Programm-Counter)

2

2

-

JMP @A+DPTR

Setze das Programm an der Stelle fort, die sich aus der Summe von Akkumulator und DPTR ergibt.

1

2

-

JZ rel

Springe relativ zur Adresse rel, wenn der Inhalt des Akkus gleich null ist.

2

2

-

JNZ rel

Springe relativ zur Adresse rel, wenn der Inhalt des Akkus ungleich null ist.

2

2

-

JC rel

Springe relativ zur Adresse rel, wenn der Inhalt des Carry-Flag gesetzt ist.

2

2

-

JNC rel

Springe relativ zur Adresse rel, wenn der Inhalt des Carry-Flag nicht gesetzt ist .

2

2

-

JB badr, rel

Springe relativ zur Adresse rel, wenn der Inhalt von badr gleich eins ist.

3

2

-

JNB badr,rel

Springe relativ zur Adresse rel, wenn der Inhalt von badr gleich null ist.

3

2

-

JBC badr,rel

Springe relativ zur Adresse rel, wenn der Inhalt von badr gleich eins ist und lösche den Inhalt von badr.

3

2

-

CJNE A,dadr,rel

Springe relativ zur Adresse rel, wenn die Inhalte von Akkumulator und dadr ungleich sind.

3

2

CY

CJNE A,#konst8,rel

Springe relativ zur Adresse rel, wenn der Inhalt des Akkus ungleich der 8-Bit-Konstanten ist.

3

2

CY

CJNE Rr,#konst8,rel

Springe relativ zur Adresse rel, wenn der Inhalt des Registers Rr ungleich der 8-Bit-Konstanten ist.

3

2

CY

CJNE

Springe relativ zur Adresse rel, wenn der Inhalt der internen Datenspeicherzelle, die durch Ri adressiert wird, ungleich der 8-Bit-Konstanten ist.

3

2

CY

@Ri,#konst8,rel

DJNZ Rr,rel

Der Inhalt von Register Rr wird um eins erniedrigt. Ist dann der Inhalt ungleich null, springe relativ zur Adresse rel.

3

2

-

DJNZ dadr,rel

Der Inhalt von dadr wird um eins erniedrigt. Ist dann der Inhalt ungleich null, springe relativ zur Adresse rel.

3

2

-

Wir können die Sprungbefehle noch ein wenig kategorisieren:

Die unbedingten Sprünge jmp, ajmp, ljmp, sjmp werden in jedem Fall ausgeführt. Das Programm wird also nach Abarbeitung des Befehls an der angegebenen Adresse weitergeführt.

Bedingte Sprünge verändern den Programmablauf nur, wenn die Bedingung erfüllt ist. Falls nicht, wird wie bei anderen Befehlen die nächste Programmzeile ausgeführt.

Wenn wir je nach Ergebnis einer Rechnung ein Programm ausführen lassen wollen, bedienen wir uns der Sprungbefehle die das Zero- oder das Carry-Flag prüfen. Wie diese je nach Rechenergebnis gesetzt oder gelöscht werden, lernen wir später kennen.

Wenn wir je nach Bitstellung etwas ausführen lassen möchten, benutzen wir die Bitprüf-Sprungbefehle JB, JNB, JBC oder wenn das Resultat einer Logikverknüpfung im Carry Flag liegt, prüfen wir direkt dieses.

Wir können mit CJNE auch direkt zwei Zahlen vergleichen und bei Ungleichheit das Programm an anderer Stelle weiterfahren.

Schlussendlich können wir auch mit einem Befehl eine Variable verkleinern und gleich zur Prüfung verwenden. Diese Befehle eignen sich besonders, wenn eine Schleife eine bestimmte Anzahl mal durchlaufen werden soll.

Ein Punkt gibt es noch zu beachten: Alle bedingten Sprungbefehle können vom Standort des Befehls her nur 127 Byte im Programmcode vorwärts oder 128 Bit rückwärts springen. Wir nennen dies relative Sprünge. Wenn wir versuchen, grössere Sprünge zu machen, wird der Assembler eine Fehlermeldung generieren. Was nun, wenn wir trotzdem weiter springen möchten? Nehmen wir an, wir wollen wenn das Carry Flag gesetzt ist an eine Adresse "AdrCarry" springen, falls Carry gelöscht ist nach "AdrNotCarry". Beide Adressen sind weit entfernt. Wie lösen wir dieses Problem?

 

jc

cont1

ljmp

AdrNotCarry

cont1:

ljmp

AdrCarry

Übung Abbruchbefehle

Die bedingten Verzweigungen geben uns ganz neue Möglichkeiten. So können wir ein Programm unter bestimmten Bedingungen beenden. Wir wollen zu diesem Thema eine Übung machen. Ziel dieser Übung ist einerseits das Kennen lernen der abweisenden und der nicht abweisenden Schleife. Andererseits lernen wir mit dieser Übung auch, wie wir Programmgesteuert die Kontrolle dem Debug-Programm zurückgeben können.

Nach einem Reset springt der Mikroprozessor mit der gewählten Konfiguration an die Adresse 0A000h. Wir können also einen Software-Reset durchführen, indem wir an dieselbe Adresse via ljmp springen. Daraus ergibt sich folgende Übung:

Erstelle zu dieser Übung zuerst ein Struktogramm und schreibe erst nachher das Programm in Assembler. Verwende auch treffende Bemerkungen hinter den Assemblerbefehlen, die sich auf die Begriffe im Struktogramm beziehen!

Zur Dokumentation dieser Übung darf der Struktogrammer und ein Textverarbeitungsprogramm verwendet werden. Ich werde die Übung einziehen und bewerten.

Lese die Schalterstellungen in eine Variable StorSwitch.

Prüfe, ob das höchste Bit gesetzt ist (Bit7). Wenn es gesetzt ist, soll das Programm abgebrochen werden, indem wir einen ljmp zu A000h machen.

Im Schleifenkörper drin soll die Variable StorSwitch an die LED's ausgegeben werden.

Wenn die Abbruchbedingung nicht erfüllt ist, soll das Programm wieder von Vorne beginnen. (also wieder die Schalter einlesen).

Realisiere die schleife als Abweisende und als nicht Abweisende Schleife.

Wie zeigt sich der Unterschied dieser zwei Schleifenarten?

Wenn Du das Programm fertig hast, darfst Du selbstverständlich Deinen Kollegen bei der Lösung helfen. Versuche aber, nur Tips, nicht aber fertige Lösungen weiter zu geben!

Viel Spass & Erfolg bei dieser Übung!

Übung Entscheidungen bei Bitverarbeitung

In der letzten Übung stellte sich die Frage, weshalb die Schalterstellungen in StorSwitch zwischengespeichert werden sollen. Der Grund liegt darin, dass wir zu Beginn einer Verarbeitungsroutine die Schalterstellung einlesen und dann einfrieren wollen, so ähnlich wie wir das in der Digitaltechnik mit D- Flipflops tun. Wenn wir während einer Routine stets die aktuellen Schalterstellungen verwenden würden, könnte während der Verarbeitung ein Schalter verändert werden und so zu unvorhersehbaren Effekten führen. Deshalb frieren wir die Schalterstellungen ein und speichern sie ab! In dieser Übung wollen wir das nun so machen.

Verfasse ein Struktogramm und das Assembler-Programm, das die aufgeführten Bedingungen einhält:

Lese die Schalterstellungen bei jedem Programmdurchgang in StorSwitch.

Das Programm soll laufen, solange Bit 7 von StorSwitch gesetzt ist.

Wenn von Storswitch.Bit6 = 0, soll Bit5 bis Bit0 von StorSwitch an die LED's ausgegeben werden.

Wenn StorSwitch.Bit6 = 1, soll Bit3

Bit0

ausgegeben werden, also nur 4

Bits.

Zusätzlich sollen aber diese 4 Bits negiert auf die LED-Bits 7 4 ausgegeben werden.

Zusätzlich soll auch StorSwitch.Bit5 geprüft werden. Ist es gesetzt, so soll StorSwitch normal ausgegeben werden; ist es gelöscht, soll StorSwitch vor der Ausgabeverarbeitung negiert werden

Diese Übung wird am Schluss der Lektion zur Bewertung eingezogen!

Übung eingeschaltete Bits zählen

Erstelle einen Bitzähler: Wir wollen an den LED's ausgeben, wie viel der Schalter auf 1 gesetzt sind. Löse diese Aufgabe wahlweise mit einer Entscheidungsstruktur, oder arbeite Dich in die Schiebebefehle ein und zähle die Bits mit Hilfe von einer For-Schleife und Schiebebefehlen.

Diese Übung dient der Vertiefung für die Fortgeschrittenen, wird also noch nicht eingezogen!

Viel Spass und Erfolg bei dieser Übungssequenz!

Übung Entscheidung bei Zahlenvergleichen

Wir haben uns die letzten Wochen mit den Entscheidungen bei Binärwerten beschäftigt. Wir konnten dort nur testen, ob ein Bit gesetzt war oder nicht. Wenn wir zwei Zahlen Z1 und Z2 miteinander vergleichen wollen geben sich mehrere Möglichkeiten. Prinzipiell können sie ebenfalls Gleich oder Ungleich sein. Wenn wir diesen Sachverhalt etwas erweitert betrachten, so können wir diesen Sachverhalt noch mehr ausreizen. Die beiden Zahlen können nämlich

Z1 = Z2 Z1 > Z2 Z1 < Z2

Z1 ist gleich Z2 Z1 ist grösser als Z2 Z1 ist kleiner als Z2

Je nach Vorgabe möchte ich auf einen oder auch auf mehrere Sachverhalte prüfen. Wie ich Gleich oder Ungleichheit prüfe ist uns wahrscheinlich schon bekannt: JZ Jump if Zero oder JNZ Jump if not Zero.

Wie vergleiche ich aber direkt zwei Zahlenwerte? Ganz einfach: Wir subtrahieren die beiden Werte voneinander. Waren sie gleich, haben wir als Ergebnis 0, und das Zero Flag ist gesetzt. Falls nicht, so waren die Operanden ungleich. Mehr noch: Ist bei einer Subtraktion von Z1 – Z2 Z1 kleiner als Z2, so kommt ein negatives Resultat raus. In diesem Fall wird das Carry Flag gesetzt. So können wir prüfen ob eine Zahl grösser oder kleiner ist.

Wenn wir ausschliesslich zwei Zahlen miteinander vergeleichen möchten steht uns dazu eine leistungsfähige Assemblerfunktion zur Verfügung, die die Zahlen prüft und gleichzeitig je nach Resultat springt:

CJNE

Compare and Jump if not equal

Wie dieser Befehl funktioniert wollen wir uns gleich an dem Auszug aus dem Datenbuch anschauen:

CJNE <dest-byte >, < src-byte >, rel

Function: Compare and jump if not equal

Description: CJNE compares the magnitudes of the first two operands, and branches if their values are not equal. The branch destination is computed by adding the signed relative displacement in the last instruction byte to the PC, after incrementing the PC to the start of the next instruction. The carry flag is set if the unsigned integer value of <dest-byte> is less than the unsigned integer value of <src-byte>; otherwise, the carry is cleared. Neither operand is affected.

Für diejenigen die in Englisch nicht so gewandt sind, ist es vielleicht geeigneter, die symbolische Beschreibung dieses Befehls zu betrachten. Wir nehmen gerade den in der Übung benötigten:

Wenn also die im Akku gespeicherte Zahl kleiner ist als der direkt eingegebene, so wird

Wenn also die im Akku gespeicherte Zahl kleiner ist als der direkt eingegebene, so wird das Carry-Flag gesetzt.

Lasst uns diesen Befehl gleich mit Aufgaben einüben. Erstelle zu jeder Übung zuerst ein Struktogramm und schreibe darauf basierend das Assemblerprogramm:

1.

Kleiner, gleich oder grösser?

Erstelle ein Programm, das die Schalterstellungen einliest und als Zahl interpretiert.

Vergleiche die eingelesene Zahl mit einer Konstanten, z.B. 30. Als Ausgabe dienen die LED's. Je nach Ergebnis sollen folgende LED's leuchten:

o

Wenn die eingelesene Zahl kleiner der Konstanten ist, soll LED.0 leuchten.

o

Wenn die eingelesene Zahl gleich der Konstanten ist, soll LED.1 leuchten.

o

Wenn die eingelesene Zahl grösser als die Konstante ist, soll LED.2 leuchten.

2.

Füllstandsanzeige:

Die Schalter dienen als Eingabemöglichkeit, um Zahlen einzugeben. Mit den LED's geben wir aus, in welcher Dekade die Zahl liegt:

Ist sie zwischen 0

usw. In diesem Sinne ist weiterzufahren bis LED.7 für 70 79. Wenn die Zahl grösser als 80 wird, sollen alle LED's miteinander leuchten, um den Überlauf anzuzeigen.

9,

so soll LED.0 leuchten. Ist sie zwischen 10

19

LED.1

3. Erstelle aus den Erkenntnissen von obiger Übung Programmblöcke für folgende Aufgaben: (es geht nur darum je eine Aufgabe mit einem Programmblock zu lösen, so dass wir später für alle möglichen Bedingungen nachschlagen können was zu tun ist)

Springe zum Label equal, wenn die beiden Zahlen übereinstimmen.

Springe zum Label notEqual, wenn die beiden Zahlen nicht übereinstimmen.

Springe zum Label LessThan, wenn die Zahl im Akku kleiner als die Konstante ist

Springe zum Label LessEqual, wenn die Zahl im Akku kleiner oder gleich der Konstanten ist.

Springe zum Label GreaterEqual, wenn die Zahl im Akku grösser oder gleich der Konstanten ist.

Springe zum Label GreaterThan, wenn die Zahl im Akku grösser der Konstanten ist.

4. Zu obiger Aufstellung können wir quasi als Modultest folgendes Programm entwickeln:

a)

Schreibe ein Struktogramm, das folgende Bedingungen erfüllt:

Lese die Schalterstellungen ein. Speichere nur bit0

bit3

in der Variablen

Zahl.

Vergleiche nun die erhaltene Zahl mit der Konstanten 7, indem Du folgendes beachtest:

 

o

Wenn Schalter bit 6 gesetzt ist, soll LED0, leuchten, sofern Zahl = 7 ist. Ist bit 6 gelöscht, leuchtet LED0, sofern die Zahl <> 7

o

Wenn Schalter bit 5 gesetzt ist, muss LED1 leuchten, sofern die Zahl > 7 ist. Ist bit 5 gelöscht, soll LED1 leuchten, sofern die Zahl < 7 ist.

Das Programm soll so lange wiederholt werden, bis Bit 7 eingeschaltet wird. Bevor wieder an den Debugger zurückgegeben wird, soll an den LED's der Code 1010'0101b ausgegeben werden, um dem Benutzer zu signalisieren, dass an den Debugger zurückgegeben wurde.

b)

Schreibe das Programm in Assembler, und demonstriere mir die Funktion

des Programmes. Anschliessend werde ich das Programm einziehen und folgendes bewerten:

Funktionalität erfüllt.

Struktogramm und Programm stimmen überein

Programm ist kommentiert, so dass sich der Leser zurechtfindet und weiss welcher Struktogrammteil gerade abgearbeitet wird.

Der Programmtest ist sinnvoll und protokolliert.

Zeitschleifen

Was machen wir, wenn wir eine LED blinken lassen wollen? Natürlich: wir schalten sie wechselweise ein und wieder aus. Nur wenn wir das direkt hintereinander machen, ist der Vorgang viel zu schnell. Wir müssen den Prozessor also eine bestimmte Zeit lang beschäftigen bevor die LED wieder umgeschaltet wird. Wir könnten dies mit den eingebauten Timern im Prozessor realisieren. Wir müssten dazu aber diese Hardware kennen lernen. Zudem ist es bei Einsatz der Timer empfehlenswert, auch Interrupts einzusetzen. Damit könnten wir den Prozessor andere Dinge erledigen lassen, anstelle die Zeit einfach abzuwarten. Diese Systeme kennen zu lernen braucht jedoch Zeit und relativ abstrakte Denkweise, die wir uns zunächst angewöhnen müssten – und je nachdem einen Logikanalysator. Nun haben wir im Moment leider keine Zeit dazu… Wir können mit verschachtelten Schleifen jedoch ähnliches erreichen:

Wir könnten z.B. eine Variable in Form einer FOR-Schleife zurückzählen lassen. Unsere Variablen sind aber nur 1 Byte gross. Wir können also gerade von 255 auf 0 herunterzählen. Nun, geschickter ist es, Schleifen zu verschachteln. Ein Befehlszyklus dauert auf unserem Prozessor 1 µs. Wenn wir 1 s warten möchten müssen wir also 1'000'000 Maschinenzyklen verstreichen lassen. Schauen wir uns also mal an, wie wir eine solche verschachtelte Zeitschleife realisieren können:

 

mov

r2, #10

; R2 = 10

1T

loop2:

mov

r3, #200

; R3 = 200

1T

¦

loop3:

mov

r4, #249

; R4 = 249

1T

loop4:

djnz

r4, loop4

; r4 bis auf 0 zählen

2T ¦|¦

djnz

r3, loop3

; loop3 ausführen bis R3=0

2T

djnz

r2, loop2

; loop2 ausführen, bis R2 = 0

2T

¦

Aufgabe: Wie lange dauert es, bis der dargestellte Code abgearbeitet wurde?

Zeitbedarf für den Code:

t = 1 us *10 * ((2*249 + 3)*200 + 3) = 1.00203 s

Zeichne ein Struktogramm zu diesem Code:

Mache ein Programm, das eine LED im Sekundentakt blinken lässt. Erstelle alternativ ein Programm, das den LED-Port im Zeittakt hochzählt.

Prozeduraufrufe

Wir haben eine Zeitverzögerungsroutine geschrieben. Vielleicht benötigen wir im Programm mehrere Zeitverzögerungen. Es ist dann relativ sinnlos, überall den erforderlichen Programmcode einzufügen. Wir können aber auch die Routine an einem bestimmten Ort schreiben und dann darauf verzweigen. Allerdings macht dies nur Sinn, wenn wir anschliessend wieder an den alten Programmort zurückspringen können. Diese Funktion erledigt uns der Befehl LCALL:

Syntax:

LCALL Zeitverzögerung

Was wird mit diesem Befehl ausgeführt? Der Auszug aus dem Handbuch verrät es uns:

ausgeführt? Der Auszug aus dem Handbuch verrät es uns: Wir sehen dass hier der Procram Counter

Wir sehen dass hier der Procram Counter PC auf dem Stack gespeichert wird. Der Stack ist ein temporärer Speicher, in dem wir eben z.B. Rücksprungadressen hinterlegen können. Nach dem Ausführen der Funktion muss dann allerdings auch speziell zurückgesprungen werden. Der zugehörige Befehl lautet

RET

zurückgesprungen werden. Der zugehörige Befehl lautet RET RET ist ein spezieller Sprungbefehl. Er spring an den

RET ist ein spezieller Sprungbefehl. Er spring an den Speicherplatz zurück, der im Stack durch den LCALL – Befehl hinterlegt wurde.

Wie funktioniert der Aufruf und wie sieht die Struktur einer Subroutine im Prinzip aus?

 

; Hauptprogramm

lcall

ClearLED

; Rufe Löschroutine auf

; weiterer Code wird nach der Routine ausgeführt

jmp

irgendwo

; Ende des Hauptprogramms

ClearLED:

mov

LED, #00h

; Lösche die LED's

ret

Übung: Mache eine Subroutine, die für die Zeitverzögerung eingesetzt wird. Mache ein Programm, wo die Zeitverzögerung mehrere Male aufgerufen wird.

Weitere Vertiefung: Mache eine Subroutine, der die Zeitdauer der Verzögerung irgendwie übergeben werden kann. Benutze dazu wahlweise einen Speicherplatz, ein Register oder auch den Stack.

Der Stack

Wir haben bereits vorher vom Stack gesprochen. Er kann zum Hinterlegen von Daten verwendet werden. Wir benutzten ihn bisher nur für den Subroutinenaufruf. Wir können darin aber auch eigene Daten ablegen. Wichtigstes Anwendungsgebiet ist das Sichern von Registerinhalten, die wir in Subroutinen verwenden. Wir wissen nämlich in einer Subroutine kaum, welche Register das Hauptprogramm verwendet hat.

Wie können wir den Stack benutzen? Es gibt dafür zwei Befehle:

PUSH und POP

Stack benutzen? Es gibt dafür zwei Befehle: PUSH und POP Mit Push speichern wir Daten auf

Mit Push speichern wir Daten auf dem Stack.

PUSH und POP Mit Push speichern wir Daten auf dem Stack. Mit POP können wir die

Mit POP können wir die Daten wieder vom Stack in eine Variable übernehmen. Beachte auch die Art, wie die Adresse im Stack-Pointer verändert wird.

Übung: Mache eine Zeitverzögerungsroutine, die nur ein Register verwendet. Benutze den Stack als Zwischenspeicher

Prozessablaufsteuerungen

Häufig werden Mikroprozessoren zur Steuerung von Anlagen eingesetzt. In solchen Anlagen gibt es sowohl Sensoren wie auch Aktoren. Die Sensoren sind Eingänge, die dem Prozessor den Betriebszustand einer Anlage mitteilen. Es kann sich dabei um Handschalter, Türkontakte, Messignale etc. handeln. Die Aktoren sind aktive Baugruppen wie Elektromotoren (z.B. Servomotor) Magnetventile oder auch nur Anzeigeleuchten.

Wir können mit unserem Prozessor also die Sensoren mit Schaltern, die Aktoren mit LED's simulieren, oder auch ganze Geräte resp. Simulationsboards von diesen verwenden. Wie kriegen wir aber die verlangte Funktionalität in ein Programm? Selbstverständlich können wir auch hier Struktogramme verwenden, um überlegen, entwickeln und festhalten zu können, was wir programmieren möchten. Allerdings sind die Pflichtenhefte für eine Automatisierung vielfach kompliziert. Eine Zustands-Ereignis Darstellung (state event diagram) kann helfen, eine Struktur in die Problemstellung hineinzukriegen. Lasst uns dies zunächst einmal an einem einfachen Beispiel erörtern:

Für einen Einkaufsladen sollen wir eine automatische Türsteuerung entwickeln. Mit einem Funktionswahlschalter sollen drei Möglichkeiten wählbar sein

Steht der Schalter auf "zu", so soll die Türe geschlossen werden resp. bleiben. Ist sie noch offen, soll unter den später beschriebenen Sicherheitsaspekten geschlossen werden.

Steht der Schalter auf "Automatik", sind die Lichtschranken zur Personenerkennung aktiv. Die Türe soll geöffnet werden, sobald sich jemand in der Lichtschranke befindet. Ist niemand mehr von der Lichtschranke erfasst, soll sie noch 5 Sekunden offen bleiben, bevor sie sich wieder schliesst.

Steht der Schalter auf "Auf", soll die Türe immer offen stehen.

Für die Motorsteuerung ist folgendes Prozedere vorgesehen:

Wenn die Türe öffnen soll, soll der Prozessor das Signal öffnen an den Türmotor schicken, bis der Endanschlagsschalter der Türe mitteilt, dass die Türe offen ist.

Wenn die Türe schliessen soll, wird dem Türmotor das Signal schliessen gesendet, bis der Endanschlagssensor meldet, die Tür sei geschlossen. Damit keine Personen eingeklemmt werden können besitzt die Tür zudem einen Überlastsensor. Spricht dieser an, ist die Türe sofort wieder ganz zu öffnen.

Wie gehen wir zur Lösung dieses Problems vor?

1. Tabelle mit Ein- Ausgangsbelegung erstellen

Wir erstellen eine Tabelle, was für Ein- und Ausgangssignale wir benötigen. In dieser Tabelle soll klar stehen, welches Bit für was steht, und was logisch 1 und 0 genau bedeutet.

Als Eingänge haben wir:

Port.Bit

Bezeichnung

genaue Beschreibung

P4.0

Hauptschalter

= Türe soll geschlossen sein 1 = Automatik oder Türe auf

0

P4.1

Automatik

1

= Automatik ein 0 = Automatik

aus. Ist der Hauptschalter auf 0, ist dieser Eingang ohne Bedeutung

P4.2

Personensensor

1 = Person erfasst.

P4.3

Tür

1 = Endanschlag aktiviert

geschlossen

P4.4

Tür offen

1 = Endanschlag aktiviert

P4.5

Überlast

1 = Aktiviert

Ausgänge für dieses Beispiel:

Port.Bit

Bezeichnung

genaue Beschreibung

P5.0

schliessen

1 = Schliessmotor aktiviert

P5.1

öffnen

1 = Öffnungsmotor aktiviert

2. Zeichnen des State- Eventdiagramms

3. Übersetzen in ein Struktogramm

Prozessor-Architekturen Der Anwendungsbereich von Mikroprozessoren ist heute sehr gross. Im Laufe der Zeit wurden

Prozessor-Architekturen

Der Anwendungsbereich von Mikroprozessoren ist heute sehr gross. Im Laufe der Zeit wurden verschiedene Bauarten entwickelt, um den Spezialitäten der Einsatzgebiete gerecht zu werden. Wir betrachten hier vorerst die Hardware- Architekturen. Ergänzende Informationen zu diesem Thema befinden sich im ASM Lehrgang S. 25 – 28 oder im Vogel Band Mikroprozessortechnik.

Die von Neumann Maschine

Prozessoren, die nur ein einziges Bussystem nach aussen besitzen nennen wir von Neumann Maschinen. Nur ein Bussystem bedeutet natürlich auch nur ein Adressbereich:

Programme und Daten liegen im gleichen Adress- oder Speicherbereich

Vorteile der von Neumann Architektur

Einfache Architektur, leicht zu verstehen

Transportbefehle (z.B. Move, Copy) finden nur in einem Adressraum statt

Weniger Leitungen nach aussen notwendig

Nachteile

Programmcode und Daten müssen nacheinander (sequenziell) eingelesen werden Mehrere Taktzyklen für ein Verarbeitungsbefehl notwendig.

Durch fehlerhaften Datentransfer kann der Programmcode zerstört werden.

Von Neumann Architekturen werden deshalb für universelle Rechen- und Steueraufgaben eingesetzt, die nicht zeitkritisch sind.

Die Harvard Maschine

Im Gegensatz zur von Neumann-Architektur besitzt ein Prozessor mit Harvard-Architektur zwei oder mehr Bussysteme. Damit können wir zwei (oder mehr) Speicher unabhängig voneinander adressieren. Wir können damit Programme und Daten in getrennten, unabhängigen Speichern ablegen.

Vorteile der Harvard-Architektur

Programm- und Datenspeicher können unabhängig voneinander adressiert und ausgelesen werden Parallelbetrieb, Programmcode und Daten können mit einem Takt gleichzeitig gelesen/geschrieben werden.

Durch fehlerhaften Datentransfer kann der Programmcode nicht zerstört werden.

Nachteile

Komplizierter Aufbau, es ist nicht immer einfach zu sehen welche Befehle auf welchen Bus zugreifen können.

Parallelbetrieb erfordert auch 'paralleles Denken': Nicht alle Kombinationen von Programm und Datenzugriffen führen zu vernünftigen Resultaten.

Die Harvard Architektur wird vor allem für zeitkritische Echtzeit- Datenverarbeitung eingesetzt. Beispiel dafür sind Signalprozessoren, die zur digitalen Signalverarbeitung verwendet werden.

Beispiel eines Signalprozessors

Signalprozessoren der Motorola DSP56K Serie besitzen mehrere unabhängige Bussysteme und gehören damit zu den Harvard-Maschinen. Im Blockdiagramm finden wir die wesentlichen Komponenten einer CPU wieder, stellen aber auch fest, dass die Verdrahtung der Einzelkomponenten viel komplexer als bei der von Neumann Maschine (Vergleiche mit dem Grundaufbau vom Z80 CPU S. 15) geworden ist.

SSppeeiicchheerrkkeennnnggrröösssseenn

Wenn wir Speicherbausteine einsetzen wollen, müssen wir mit einigen Begriffen vertraut sein, um den für uns geeigneten Speicher finden zu können. Einige von ihnen sind hier erläutert.

Speicherkapazität

Sie gibt die Anzahl der in der Speicherschaltung enthaltenen Speicherzellen an, also die Anzahl der speicherbaren Bit. Einige Beispiele:

1 kBit = 2 10 Bit =

1 MB =

Speicherorganisation

Die Speicherorganisation gibt Auskunft über die Speicherkapazität einer Speicherzelle und über die Auswahlmöglichkeit. Die Bezeichnung 128 x 8 bedeutet dass 128 Speicherzellen mit je 8 Bit (=1 Byte) vorhanden sind. Mit einer Adressierung werden also gleichzeitig 8 Bit ausgelesen resp. geschrieben.

Beispiele:

256

k

x 1 =

128

k

x 8 =

1

M

x 4 =

256

k

x 32 =

Zugriffszeit

Sie ist die Zeit, die vom Zeitpunkt der Adressierung einer Speicherzelle bis zur Verfügbarkeit der Information am Datenausgang vergeht.

Zykluszeit

Kürzeste Zeit zwischen zwei aufeinanderfolgenden Schreib-Lese-Vorgängen

Speichereinsatz in der Praxis

Speicherschaltungen haben wir vom Prinzip her schon in unseren Vorträgen kennen gelernt. Hier wollen wir uns noch damit beschäftigen, wie wir auf

einen Speicherbaustein von aussen her zugreifen können. Wir werden uns dies mit zwei Beispielen etwas verdeutlichen:

Wir werden uns dies mit zwei Beispielen etwas verdeutlichen: Wir sehen, dass der aufgezeichnete Speicherbaustein

Wir sehen, dass der aufgezeichnete Speicherbaustein

einerseits Adressleitungen (A0

Datenleitungen (D0

benötigen wir auch noch einige Steuerleitungen. Was können wir aus diesen Daten herausfinden?

A5) und

D7)

besitzt, andererseits

Speicherorganisation Wir haben 6 Adressleitungen und können damit folglich 2 6 = 64 Speicherplätze adressieren. Pro Speicherplatz sind 8 Bit vorhanden. Es handelt sich folglich um einen Speicher mit der Speicherorganisation 64 x 8 Wie gross ist der Adressbereich des Speichers? Dies können wir mit der Gegenfrage beantworten, welche Zahlen wir mit 6 Bit darstellen können:

00'0000b

11'1111b = 0

63

= 00h

3Fh

Das zweite Beispiel zeigt uns die Umkehrung der Frage: Ich kenne von einem Speicher die Bezeichnung 8k x 8. Wieviele Daten- und Adressleitungen besitzt dieser Speicher und wie sieht der Adressbereich aus?

8k = 81024 = 8192 Speicherplätze à 8 bit (D0 D7)

Adressbereich 0

8192 = 2 n log(8192) = nlog(2) n = log(8192)/log(2) = 13 Adressleitungen A0

Einige Rechenbeispiele

8191 = 0h

1FFFh

A12

1. Ein PC zeigt beim Aufstarten: 134'217'728 Bytes OK. Wieviel RAM in MB

hat dieser PC installiert? Wievielen Speicherplätzen à 32 Bit entspricht dies

(entsprechend X x 32 Rambausteinen)

2. Reche folgende Speichergrössen um:

512k x 16 = ……………… Bytes, ……………. MB