Keine Angst vor Assembler

Status
Für weitere Antworten geschlossen.

oldmax

Mitglied
Premium Benutzer
03. Nov. 2008
595
15
18
Landkreis Göttingen
Sprachen
  1. Assembler
Hi
Nun, da ich mich zur Zeit mit den Tests etwas schwer tu, will ich aber trotzdem schon mal eine kleine Übersicht und einleitende Worte liefern
Der µController und Assembler Seite
1. Einleitung 3
1.1 Grundlagen Elektrotechnik 4
1.2 Ein wenig Information zu Zahlen 5

2. Aufgabe skizzieren 6

3. Aufbau Controller 7
3.1 Speicher 7
3.2 Register 8
3.3 Counter / Timer 9
3.4 Der UART, das Verbindungsglied 10
3.5 Ports, die Schnittstelle der Signale 11
3.6 Portbeschaltung 12
3,7 Initialisierung Port 13

4. Keine Angst vor Assembler 14

5. Die Programmstruktur
5.1 Die Compilerdirektriven 15
5.2 Die Variablendeklaration 16
5.3 Die Interrupt Vector Tabelle 17
5.4 Initialisierungsabschnitt 18
5.5 Initialisierung Stack 18
5.6 Initialisierung durch Unterprogramme 19
5.7 Programmschleife 20
5.8 Unterprogramme 21
5.9 Interrupt 22
5.10 Interrupt Service Routine Timer 22
5.11 Verzögerung mit Timer und Flags 23
5.12 Interrupt Service Routine UART 24

6. Das Projektprogramm
6.1 Gliederung 25
6.2 Signalerkennung 26
6.3 Signalauswertung 27
6.4 Bitmanipulation 29
6.5 Empfang auswerten 30
6.5.1 Prüfen auf Mehrbyte-Befehle – Blockempf. 31
6.5.2 Empfang Datenblock 32
6.5.3 Empfang Datenblock variabel 33

6.6 Status und Kontrollflags 34
6.7 Auswertetabelle 35
6.8 Daten senden 36

7. Eingänge 37
7.1 Eingänge verdoppeln / Matrix 37
7.2 Eingange verfielfältigen 38
7.2.1 Die Auswahl der Eingangskanäle 39
7.2.2 Einlesen der Eingangskanäle 39
7.2.3 Auswerten von Änderungen der Eingänge 40
7.2.4 Änderungen der Eingänge eintragen 41
7.2.5 Multikanal-Flankenauswertung 42
7.3 Interrupt für Eingangssignale 43

8. Arbeiten mit Tabellen 44
8.1 Tabellen auswählen 45
8.2 Tabelleninhalte versenden 46
8.3 Parametrierbare Unterprogramme 47
8.4 Tabellen kopieren 48

9. Programmlaufzeiten 49
9.1 Mit 2. Timer Laufzeit berechnen 50

10. Das gesamte Programm 51
Soweit die Übersicht.
 
Einleitung

1. Einleitung
Zuerst möchte ich ein paar Fragen stellen, die sich jeder selbst beantworten muß.
Warum will ich die Controllertechnik lernen
Wozu will ich sie einsetzen.
Wie viel Geld will ich investieren
und schließlich nicht ganz unwichtig:
Wie viel Zeit kann und will ich opfern

An den Antworten werdet ihr erkennen, ob es lohnt, weiterzugehen. Bitte, nicht einsteigen wollen, um Cool zu sein. Ich garantiere euch, ein Kinobesuch bringt mehr als die Hoffnung, hier mit C&P vor Freunden zu glänzen.

Nun zum Thema und Inhalt
Der Inhalt soll bisher veröffentlichte Informationen ergänzen. Es ist unmöglich, alle Bereiche einer Assemblerprogrammierung in eine Programmstruktur einzubauen. Daher werde ich versuchen, ein paar Hinweise zum „ Wie macht man so was „ zu geben.
Grundlage für Arbeiten mit Controllern sind in erster Linie die Datenblätter des Herstellers. Dort sind unter anderem die Pinbelegung, die Features und ein Funktionsbild hinterlegt. Dieses Datenblatt zu erklären würde den Umfang dieser kleinen Information sprengen und es sollte genügen, auf aktuelle Punkte einzugehen.
Der Inhalt wird erklären, wie ein Programm in der Regel entsteht, welche Struktur man zugrunde legt und was es mit der Blackbox auf sich hat.
Ein paar Grundlagen zu Elektrotechnik werden wir auch ansprechen.
Der Inhalt sollte als Basis für beliebige Anwendungen dienen und viel Spielraum für eigene Ideen geben. Am Ende wird also kein fertiges Programm stehen, sondern nur ein nützlicher Rahmen. Assembler zu lernen ist nicht besonders schwer, aber es darf trotzdem nicht erwartet werden, das nach Durcharbeiten dieser Lektüre alles gesagt ist. Der Befehlsumfang ist mit etwas über 100 Anweisungen relativ gering und diese zu lernen nicht besonders schwierig. Die größten Hürden sind wohl darin begründet, das es eben keine vorgefertigten Routinen gibt, die ganz selbstverständlich auf einfachste Weise Ergebnisse liefern. Nehmen wir nur einmal die Float-Mathematik.
In Basic einfach A=4,3434 * 7,4534 / 5,343 und gut. In Assembler ist das eine Herausforderung. Nicht, das es unmöglich wäre.... denn schließlich ist auch die Basic-Anweisung nichts anderes, als LOAD und ST ORE.
Es ist natürlich auch die Anwendung, die mit über die eingesetzte Software entscheidet. Wenn mein Controller aber nur Eingänge verarbeiten soll und Ausgänge schalten, dann ist Assembler grad richtig, um einen Einblick in die Controllerwelt zu erhalten.





Weitere Informationen unter :

http://www.avr-praxis.de/database/mc-database.php
http://www.mikrocontroller.net/articles/AVR-Tutorial
 
Der Stromkreis

1.1 Grundlagen Elektrizität

Im Prinzip reden wir immer von einem Stromkreis. Dieser besteht aus einer Quelle G, einem Widerstand R und einem Schalter S


In der Elektrotechnik gibt es zwei Arten von Spannungen, die bedingt durch die Erzeugung entstehen. Unser Versorgungsnetz wird durch Generatoren erzeugt, d. h. durch Elektromagnetismus. Diese Form der Spannung nennt man Wechselstrom.
Sie verläuft zeitlich in Sinusform. Analog zum Spannungsverlauf bewegt sich der Strom mal in die eine oder andere Richtung. Auch der bekannte Drehstrom ist Wechselstrom.
Die Gleichspannung kennt man von der Erzeugung her aus chemischen Prozessen. z.B. die Batterie oder der Akkumulator. Hier fließt der Strom nur in eine Richtung.
Elektronische Schaltungen arbeiten in der Regel mit Gleichspannung.

Damit überhaupt ein Strom fließen kann, muß eine Spannungsquelle mit einem Widerstand kurzgeschlossen sein. Ist der Widerstand klein, so ist der Strom hoch. Ist ein großer Widerstand im Stromkreis, ist der Strom niedrig. Somit verhält sich der Strom entgegengesetzt zum Widerstand und man kann die Werte durch die
Formel I=U/R berechnen.
Wir werden diese Formel sehr oft benötigen. Also, auch ein wenig Mathe wird euch hier und da schon mal abgefordert. In unserem Kurs allerdings wollen wir aber wirklich nur das Notwendigste behandeln.
Da wäre beispielsweise der Vorwiderstand bei einer LED. Der Strom sollte mit 10 mA in den Standartanwendungen ausreichen, Wir machen es mal nicht genau und betrachten nur die 5 V und wollen 10 mA. Daher stellen wir die Formel um nach R, denn den Widerstand wollen wir ja berechnen. R=U/I das ganze mal mit Zahlen R=5V / 0,010 A = 500 Ohm. Nun werden mir die Experten aber gewaltig auf die Finger klopfen, wie ich die Diodentypische Flussspannung vernachlässigen. Na ja, ich will sie ja nur leuchten sehen, und wenn ich keinen 500 Ohm Widerstand habe, kann ich auch bspw. einen 330 Ohm einsetzen.....
Viel tiefer will ich hier aber nicht einsteigen. Wer weitere Information benötigt, sollte sich selbst darüber informieren. Über alle Themen gibt es im Netz ausreichend ausführliche Information. Hier ist mir nur wichtig, das Prinzip Stromkreis verstanden zu haben.
Na ja, die Skizze ist wirklich mikrich....aber völlig ausreichend.
 

Anhänge

  • Stromkreis.jpg
    Stromkreis.jpg
    4,5 KB · Aufrufe: 46
Zahlen

1.2 Ein wenig Information zu Zahlen
Auch wenn wir von Bits und Bytes reden, steckt immer eine elektrische Größe dahinter. Ein 1-Signal ist in der Regel VCC und ein 0-Signal GND Potential. Dabei sage ich absichtlich nicht die Spannungshöhe. Sie spielt nämlich für die 1 in einem Bit überhaupt keine Rolle. In einem µC reden wir immer von Bits und Bytes. 1 Bit ist die kleinste elektronische Speichereinheit. Ein Byte ist die Zusammenfassung von 8 Bit. Ein Word hat 2 Byte, also 16 Bit. Bei der Zählung fängt man immer mit Bit 0 an. Da ein Bit nur 2 Zustände haben kann, 0 und 1 ergibt sich die Wertigkeit eines Bit in 2^0. Von rechts nach links gelesen, genau wie im bekannten Dezimalsystem ist im Exponenten die Stelle, also 2^0 , 2^1 , 2^2 usw. ergibt 1, 2, 4, usw an Stellenwert.
Im Dezimalsystem kennen wir das auch, allerdings mit Basis 10, also 10^0, 10^1, 10^2 usw.
So wird die Zahl 2534 gebildet aus
4 * 10 ^0 + 3 * 10 ^1+ 5 * 10 ^2 + 2 * 10 ^3
Eine Binäre Zahl setzt sich genauso zusammen, nur die Basis ist eben die 2
11010110 ist ausgerechnet
0 * 2^0 + 1 *2^1 + 1 *2^2 + 0 *2^3 + 1 *2^4 +0 * 2^5 + 1 *2^6 + 1 *2^7
0 +2 +4 +0 +16 +0 +64 +128 =214
Auf diese Weise sind in einem Byte exakt Werte von 0 bis 255 also 256 Werte darstellbar.Es ist wichtig zu wissen, wenn bei µC von Adressierung unf Zählung geredet wird, ist 0 die Zahl, mit der begonnen wird. Daher kann man leicht aus einer Dualzahl auf die höhstmögliche Zahl kommen. Ein Byte hat 8 Bits, also 8 Stellen. Ergo ist die höchstmögliche Zahl 2^8 -1 = 256-1 .
Ein Wort hat 16 Stellen, also ist hier die höchstmögliche Zahl 2^16-1= 65536-1.
Auch wenn hier für viele nur alte Kamellen angesprochen werden, es gibt ja auch Anfänger, die bisher nichts von alledem wussten. Alte Hasen wissen, was eine Hexadezimalzahl ist, aber ich lese allzu oft, das nicht jeder diese Form der Zahlendarstellung kennt. Auch diese soll nicht unbeachtet bleiben, daher werde ich sie hier ebenfalls erklären:
Die Hex-Zahl 3D7E wird wie alle Zahlen von rechts nach links bewertet. Hexa heißt nix anderes wie Basis 16. Basis 2 und Basis 10 kennen wir ja schon, doch wie bitte funktioniert basis 16. Es gibt doch nnur Ziffern 0-9. Richtig, deshalb müssen wir Teile vom Alphabet bitten, einzuspringen. Es sind die Buchstaben A-F.
A=10, B=11, C = 12, D = 13, E =14 und F=15. Nun verstehen wir auch die Hex-Zahl 3D7E
Also 14 * 16^0 + 7 *16^1+ 13*16^2+ 3*16^3 = 14 + 112 +3328 + 12288 =15742
Aus einer Hex-Zahl eine Binärzahl abzuleiten ist gar nicht schwer. Um Zahlen von 0 – 15 darzustellen braucht es 4 Bit. Also besteht eine 4 stellige Hexzahl aus 4 * 4 Bit oder anders aus zwei Byte oder einem Wort.
3 =0011 = 1*2^0+1*2^1 =3
D =1101 = 1*2^0+1*2^2+1*2^3 =13
7 =0111 = 1*2^0+1*2^1+1*2^2 =7
E =1110 = 1*2^1+1*2^2+1*2^3 =14
Also ist eine Hex-Zahl auch ein Binärwert von 0011110101111110
Alles klar ?
Ich erwarte nicht, das ihr das gleich versteht, aber behaltet das erst mal im Hinterkopf.
 
Controller und Asembler

2. Das Thema lautet ja, µC und Assembler.
Um zu verstehen, warum überhaupt ein Controller eingesetzt wird, betrachten wir einmal eine mögliche Anwendung:
Wir haben einen PC und möchten damit ein Programm zur Steuerung und Überwachung unserer häuslichen Umgebung entwerfen. Kenntnisse in Visual Basic, C oder Delphi liegen vor. Da PC’s nicht mehr direkten Zugriff auf die Peripherie zulassen, muß man sich externer Geräte bedienen, die Informationen aus der Außenwelt in den PC tragen. Ein Controller kann relativ einfach eine serielle Verbindung zum Datenaustausch mit einem PC Programm herstellen. Also wird eine Schaltung entworfen, die Werte erfasst und Werte ausgibt. Die Bedingungen gibt der PC vor.
Nun kann ich euch nicht in diesem kleinen Rahmen auch noch sagen, wie ein Programm auf dem PC aussieht, aber ich habe bereits ein Programm hier veröffentlicht, welches direkt mit einem Controller komuniziert. Dieses nehme ich zur Grundlage, um die Mechanismen zu erklären und damit ein Assemblerprogramm aufzubauen.
An erster Stelle sind Skizzen für Programm und benötigte Hardware. Dazu reicht ein Blatt Papier oder wenn’s geht, auch Powerpoint oder ähnliche Programme.
 

Anhänge

  • Blockbild.jpg
    Blockbild.jpg
    16,1 KB · Aufrufe: 71
  • BlockµC.jpg
    BlockµC.jpg
    8,6 KB · Aufrufe: 53
Controllerstruktur

3. Aufbau Controller

3.1 Speicherbereiche
An dieser Stelle möchte ich auf die Gemeinsamkeiten der Controller zu sprechen kommen. Auch wenn der Unterschied zwischen den Controllertypen gewaltig ist, kann man doch von einer gemeinsamen Basis ausgehen.
Alle Controller haben verschiedene Speicher. Da ist der Programmspeicher. Er behält bei Spannungsausfall seine Information. Das ist nützlich, schließlich möchte man ein Gerät ausschalten können, Batterie wechseln oder andere Tätigkeiten durchführen, wo der Controller spannungslos ist. Allerdings hat dieser Flash-Speicher einen gewaltigen Nachteil, er wird bei jedem Schreibvorgang ein wenig „beschädigt“. Nach ca. 10000 Schreibvorgängen hat der Controller keine Sicherheit für eine einwandfreie Funktion mehr. Aus diesem Grund besitzt der Controller einen Speicherbereich, den SRAM, der diesen Nachteil nicht hat. Allerdings ist hier die Information bei einem Spannungsausfall verloren.
Zum Hintergrund:
Beim Programmspeicher geht man nicht davon aus, das er ständig neu beschrieben wird. 10000 Schreibzyklen sind bei der Programmentwicklung vielleicht noch zu erreichen, aber wenn ein Programm funktioniert, wird es nur einmal auf den Controller geflasht. Der SRAM dient dem Controller aber als „Schmierzettel. Da müssen Werte gelesen, zwischengespeichert und bearbeitet werden. Letztlich vielleicht sogar für den nächsten Durchlauf abgelegt werden. Dies bedeutet ein ständiges Beschreiben des Speicherbereiches. Auch müssen Werte auf den Stack abgelegt werden, wenn Interruptprogramme ausgelöst werden. Da wäre es schnell mit dem Flash vorbei und der Controller reif für die Tonne. Der Informationsverlust im SRAM ist aber ohne Bedeutung, da er sich sowieso ständig ändert und die Werte im Programm gebildet werden.
In einigen Controllern gibt es noch den EEProm. Auch er unterliegt dem „Verschleiß“ bei Schreibzugriffen, allerdings ist erst nach 100000 Schreibzugriffe die Zuverlässigkeitsgrenze erreicht. Dieser Speicher kann genutzt werden, wichtige Daten beim Erkennen von einem Spannungsausfall zu „retten“. Dazu überwacht man die Einspeisung und puffert mit einem Akku die Stromversorgung. Kommt es zum Spannungsausfall, so kann der Controller noch mit dem Accu betrieben werden und die wichtigen Daten ablegen. Des weiteren wird der EEprom auch benutzt, um Parameter für das Programm zu hinterlegen, die sich durchaus auch öfters ändern können.
 
REgister

3.2 Die Register
Nur über Register ist ein Controller in der Lage, in einer ALU ( Aritmetik- Logik Unit) Werte zu bearbeiten. Es geht nicht, zwei Speicherzellen zu addieren, die Werte müssen erst in die Register, um diese Aufgabe zu erfüllen. Allerdings sind auch genügend Register verfügbar. Der Atmega 8 z. B. hat 32 *8 Bit Register für solche Aufgaben, allerdings sind die ersten 16 Register, Register 0 bis Register 15 nicht von jedem Assemblerbefehl zu nutzen.
Die Register 26 und 27, Register 28 und 29 sowie Register 30 und 31 sind für Adressierzwecke als 16 Bit Registern verwendbar


Weitere Register sind:
Der Stackpointer, er hat eine Breite von 16 Bit, denn er muß den Adressraum ansprechen können. Dieses Register ist sofort nach Start, also direkt am Begin des Programmes zu mit der Adresse des Letzten Bytes im SRAM zu initialisieren.
ldi R26,high(RAMEND) ; Stack Pointer setzen
out SPH,R26 ; "RAMEND" ist in m8def.inc deklariert
ldi R27,low(RAMEND) ;
out SPL,R27
Auffällig ist, das der Stackpointer nicht direkt beschrieben werden kann, sondern den Umweg über andere Register nehmen muß. Das liegt daran, das dieses Register im sogenannten IO-Adressbereich liegt und über In bzw. Out gelesen bzw. beschrieben werden kann.

Das Statusregister, in dem Informationen über logische und arithmetische Operationen in einzelnen Bits abgelegt werden.
So gibt es noch das Interuptregister, hier sind Bits abgelegt, die es ermöglichen, die Interrupts zu steuern, freizugeben oder zu sperren.
Auch die Ports sind letztlich 8 Bit Register, wobei nicht alle Bits nach außen geführt sind.
Alle Register zu nennen ist nur unter Bezugnahme auf bestimmte Controller möglich. Wer tiefer einsteigen will, kommt um das Datenblatt sowieso nicht herum und dort steht alles genau drin.
Außerdem sind in den Foren Tutorials und Beschreibungen vorhanden, so das hier nur kurz und oberflächlich darauf eingegangen wird.
 
3.3 Counter / Timer
Kommen wir nun zu den Countern / Timern. Dies sind Zähler, die den Systemtakt direkt zählen. Da er ziemlich stabil ist, wird dies für zeitabhängige Bearbeitung genutzt.
Je nach Parametrierung wird ein Interrupt bei Überlauf (Overflow) oder Vergleich mit einem Vorgabewert (Compare) ausgelöst.
Timer 1 ist ein 16 Bit Timer
TCCR1B
+------+------+------+------+------+------+------+------+
| ICNC1| ICES1| | WGM13| WGM12| CS12 | CS11 | CS10 |
+------+------+------+------+------+------+------+------+
CS12 - CS10 Clock Select
CS12 CS11 CS10 Bedeutung
0 0 0 keine (Der Timer ist angehalten)
0 0 1 Vorteiler: 1
0 1 0 Vorteiler: 8
0 1 1 Vorteiler: 64
1 0 0 Vorteiler: 256
1 0 1 Vorteiler: 1024
1 1 0 Externer Takt vom Pin T1, fallende Flanke
1 1 1 Externer Takt vom Pin T1, steigende Flanke
ICES1 Input Capture Edge Select
ICNC1 Input Capture Noise Canceler

Bevor ein Timer genutzt werden kann, muß er initialisiert werden:
Code:
Init_Timer1:		
	LDI	Temp, high( 2000 - 1 )
	OUT	OCR1AH, Temp        
	LDI	Temp, low( 2000 - 1 )        
	OUT	OCR1AL, Temp    
		; CTC Modus einschalten                                   
			; Vorteiler auf 8      
	LDI	Temp, ( 1 << WGM12 ) | ( 1 << CS11 )        
	OUT	TCCR1B, Temp         

	LDI	Temp, 1 << OCIE1A   ; OCIE1A: Interrupt bei 
                                                       ; Timer Compare        
	OUT	TIMSK, Temp
Ret
Auch hier liegt das Register im IO Bereich und muß über Out gesetzt werden. Mit dieser Einstellung ist der Timer für ein Interrupt bei Vergleich eingestellt. Zusammen mit dem Vorteiler von 8 erhalte ich bei 16 MHz ziemlich genau einen Interrupt im ms-Takt. Eine Anpassung auf einen Anderen CPU-Takt ist durch Änderung vom Wert in dem Register OCR1A leicht möglich. Es muß nur darauf geachtet werden, das der Vorteiler eine ganze Zahl aus den Systemtakt liefert.

Weitere Informationen im Internet:
http://www.uni-koblenz.de/~physik/informatik/MCU/Timer.pdf
http://www.mikrocontroller.net/articles/AVR-Tutorial:_Timer
 
Uart

3.4 Der UART, das Verbindungsglied

Bleibt noch der UART zu erwähnen. Nicht alle besitzen ihn, aber trotzdem möchte ich ihn hier schon erwähnen. Er dient zur seriellen Kommunikation mit anderen Baugruppen, z. B. mit einem PC.
Auch er wird im Programm initialisiert und auch dafür gibt es eine kleine Routine.
Das Register UBRR ist das Baudratenregister. Der Wert errechnet sich nach der Formel
UBBR = (Taktfrequenz /16* Baudrate)-1. Bei 16 MHz und einer Baudrate von 2400 Baud ergibt dies einen Wert von
UBBR = (16000000/16*2400)-1 = (1000000/2400)-1 =416 -1 also 415 oder 19F Hex.
Über eine Compiler- Direktive kann man eine Variable entsprechend setzen und diese dann in das Baudratenregister eintragen.
Code:
. EQU	UBRR_VAL	 = (16000000/16*2400)-1

Auch der Uart braucht eine Initialisierung.
Code:
;-------------- Serielle Schnittstelle parametrieren  -------------------
INIT_UART:
	LDI	Temp, HIGH(UBRR_VAL)	; Baudrate einstellen
	OUT	UBRRH, Temp
	LDI	Temp_Reg, LOW(UBRR_VAL)
	OUT	UBRRL, Temp
	; Frame-Format: 8 Bit
 	LDI	Temp, (1<<URSEL)|(3<<UCSZ0)
	OUT	UCSRC, Temp
 	SBI	UCSRB, TXEN				; TX aktivieren (enable)
	SBI	UCSRB, RXCIE           			; Interrupt bei Empfang    
	SBI	UCSRB, RXEN 		 		; RX (Empfang) aktivieren (enable)
				
Ret

Der Wert aus der Gleichung nach dem Komma wird einfach abgeschnitten. Nun ergibt dies eine kleine Ungenauigkeit, die unter Umständen die Übertragung stören könnte.
In den Compiler- Direktiven wird in einem Beispiel gezeigt, wie man prüfen kann, ob eine ausreichende Genauigkeit mit den Betriebsparametern eines Controllers gegeben ist.
(s. Abschnitt Compilerdirektiven)

Durch die Freigabe des Interrupts beim Empfang eines Bytes müssen wir natürlich auch eine Routine schreiben, die diesen Interrupt behandelt und den Empfangenen Wert bearbeitet. Welche Schritte dazu notwendig sind, wird im Programmierabschnitt beschrieben.
Auch hier weitere Information unter
http://www.mikrocontroller.net/articles/AVR-Tutorial:_UART
 
Ports

3.5 Ports, die Schnittstelle der Signale
Eigentlich sind die Ports auch nur Register, die im IO Adressbereich liegen. Allerdings sind diese Speicherzellen nach außen geführt, wo sie beschaltet werden können. Diese Beschaltung ist sehr universell.
Es besteht die Möglichkeit, einen Portpin als Ausgang zu definieren. Wird das zugeordnete Bit gesetzt, dann steht am Pin eine Spannung von VCC, in der Regel 5 V an. Diese kann über Treiberstufen verstärkt werden, so das auch größere Lasten geschaltet werden können, z.B. Relais.
Auch sind gezielt Portpins als Eingänge zuzuweisen. Damit ist es möglich, Signale mit max. VCC in den Controller einzulesen. Im Controller stehen diese dann mit logisch 1 und 0 zur weiteren Bearbeitung zur Verfügung. Zusätzlich ist es möglich, sogenannte PullUp -Widerstände zuzuschalten.
Hierzu ein kurzes Beispiel:
Ein Taster wird einseitig auf GND geschaltet, der andere Anschluß kommt auf einen Eingangspin.
Wird der Taster betätigt, ist klar, ich habe 0 Potential am Eingang. Lasse ich los, dann habe ich keine elektrische Bindung und der Eingang hängt elektrisch in der Luft. Bereit, auf alles Mögliche zu reagieren. Wird er mit den Fingen berührt, ist es wahrscheinlich, das er mit 50 Hz hin- und herzappelt. Das ist aber nicht erwünscht, außerdem möchten wir ja auch den Unterschied in einer stabilen 0 oder 1 sehen. Daher werden an den Pin Widerstände gegen VCC geschaltet. Also steht bei losgelassenem Taster die 5 V über den Widerstand an. Damit nun nicht immer diese Widerstände von außen an den Controller angeschlossen werden müssen, sind diese Pull-Up Widerstände integriert und können durch Parametrierung zugeschaltet werden.
Will man aber die Taster nach VCC schalten, darf der interne Pull-Up Widerstand nicht zugeschaltet, sondern es muß ein externer Pull-Down nach GND geschaltet werden.
 

Anhänge

  • IOAnschl.jpg
    IOAnschl.jpg
    8,2 KB · Aufrufe: 39
Portbesschaltung

3.6 Portbeschaltung
Port-Register sind wie der Name schon sagt, Schnittstellen für die Außenwelt. Diese sind sehr unterschiedlich. Da gibt es die digitalen Ein- und Ausgänge. Einige Pins sind auch als Analog- Eingang und Analog Ausgang verwendbar. Außerdem greift auch der USART über die IO-Anschlüsse auf die Außenwelt zu. Es gibt auch weitere Sonderfunktionen, die programmierbar sind, aber hier zu weit führen.
Im Belegungsplan des Controllers ist erkennbar, welche Aufgabe mit welchen Pins erledigt werden kann.
In einer Skizze ist der Belegungsplan vom Atmega8 dargestellt.


Nun möchte ich euch eine Initialisierung der Ein – und Ausgänge vorschlagen, die euch 6 Ausgänge sowie 8*4 Eingänge unter Verwendung von Kaskadierung mittels 4 Ausgabebits bringen. Hier die Skizze dazu
 

Anhänge

  • Controller.jpg
    Controller.jpg
    16 KB · Aufrufe: 35
  • Multiport.jpg
    Multiport.jpg
    17,9 KB · Aufrufe: 42
Initialisierung Port

3.7 Initialisierung Port
Code:
;**********************************************
;*                            Portzuordnung                                *
;* Port C 0 und 1 sowie Port D 2 bis 7 = Eingang                  *
;* Port C 2 bis 5  sowie Port B 0 bis 5 = Ausgang                 *
;**********************************************

Init_Port:
	IN	Temp, DDRB
	ORI	Temp, 0b00111111 ;Bit 0-5 Ausgang, 6 und 7 bleibt
	OUT	DDRB, Temp	;Daten –Richtungs Register Port B
 	IN	Temp, DDRC
	ORI	Temp, 0b00111100	;Bit 2-5 Ausgang, andere Bits bleiben
	OUT	DDRC, Temp	
	IN	Temp, DDRC
	ANDI	Temp, 0b11111100	;Bit 0 und 1 Eingang, 2-7 bleiben
	OUT	DDRC, Temp
	LDI	Temp, 0b00000011	;Bit 0 und 1 PullUP Setzen
	OUT	PortC, Temp
	IN	Temp, D
	ANDI	Temp, 0b00000011	;Bit 2 bis 71 Eingang, 0 und 1 bleiben
	OUT	PortD, Temp
	LDI	Temp, 0b11111100	;Bit 2-7 PullUP Setzen
	OUT	PortD, Temp
RET
Auch hier kann ich auf eine weiterführende Information verweisen.
http://www.mikrocontroller.net/articles/AVR-Tutorial:_IO-Grundlagen
 
4. Keine Angst vor Assembler
Um einen Controller so richtig kennen zu lernen, ist die Assembler-Sprache das richtige Werkzeug. Sie bewegt sich direkt an der Hardware und ist deshalb auf einen Controller zugeschnitten. Sicherlich gibt es Bereiche, die einen Einsatz von Bascom oder C durchaus rechtfertigen und Assembler in die Schranken weisen, aber für unser Vorhaben ist es sinnvoll, mit Assembler zu beginnen.
In erster Linie ist dieser Kurs zum Erlernen von Programmiertechniken am praktischen Beispiel ausgelegt. Die Programmiersprache wird hier keineswegs vertieft oder im Detail behandelt. Lediglich notwendige Programmstrukturen werden ausführlich erläutert, sowie die notwendigen Informationen zu den Eigenheiten der verschiedenen Register angesprochen.
Um den Einstieg in Assembler ein wenig zu erleichtern, möchte ich eine kleine Hilfe geben:
Wer bereits mit Hochsprachen wie BASIC oder PASCAL programmiert hat, der weiß, das die Befehle ausgeschrieben sind und wer ein wenig englisch versteht, weiß sofort, was gemeint ist. Bei Assembler tut man sich oft schwer, weil da nur Abkürzungen stehen, die dann auch noch allein durch einen Buchstaben differenziert völlig andere Arbeitsschritte einleiten.
z.B. LDI und LDS sowie einfach nur LD
Alle sind Lade-Befehle.

LDI -> lade direkt
LDI Temp, 99 -> Lade Register mit 99

LDS ->Load from SRam -> Lade Wert aus Speicherzelle
LDS Temp, My_Bytewert-> Lade Register mit Wert aus Speicherz. My_Bytewert

MOV ->bewege, kopiere -> das geht nur mit einem Wert aus einem Register
MOV Temp, Merker -> Lade Register 1 mit Wert aus Register 2

Diese Bedeutung müssen wir lernen zu verstehen und es hilft, wenn im Kopf tatsächlich „Lade“ oder „Load“ gesprochen wird. Gleiches gilt für St (Store ) abspeichern, JMP (Jump) Springe.
Eine Vielzahl der Assemblerbefehle lassen sich so ableiten. Dadurch wird es schon etwas einfacher, und bei den Exoten schaut man halt das ein oder andere Mal in die Hilfe oder auch in die Dokumentation des Controllers. Dort ist sein Befehlssatz entsprechend dem Typ angegeben.
Uns nimmt aber das AVR-Studio mit der eingebauten Hilfe zum Assembler diese Arbeit ab.

Bei Vergleichen werde ich die Dezimalzahlen oder ASCII Zeichen benutzen, bei logischen Verknüpfungen und Verarbeitung das Binärformat.
Code:
LDI	Temp, ( 1 << WGM12 ) | ( 1 << CS11 )
OUT	TCCR1B, Temp
ist nach der Beschreibung des Registers
TCCR1B

+------+------+------+------+------+------+------+------+
| ICNC1| ICES1| | WGM13| WGM12| CS12 | CS11 | CS10 |
+------+------+------+------+------+------+------+------+
vergleichbar mit
Code:
IN	Temp, TCCR1B
ORI	Merker, 0b00001010
Out	TCCR1B, Temp
Ich denke, das gerade ein Anfänger so besser versteht, wie eine binäre Verknüpfung funktioniert.
 
Programmstruktur, Compilerdirectriven

5. Die Programmstruktur
5.1 Die Compilerdirektiven
Compilerdirektiven beginnen mit einem Punkt und einem Befehl
Code:
.NoList
.include "m8def.inc" ; Definitionen für ATMEGA8
.List
Zuerst wird das Listing abgeschaltet, dann eine externe Datei eingebunden.
In dieser Datei sind die spezifischen Controllerdaten und Deklarationen enthalten. Jeder Controller hat so seine eigene Datei. Anschließend wird das Listing wieder geöffnet. Jetzt holt der Compiler alle Informationen aus dem nachfolgenden Text.
Wir starten mit der Directive
.DEF und vergeben symbolische Namen an Register und Bits. Damit ist es im Listing leichter, spezielle Register und Bits, die eine bestimmte Aufgabe erhalten haben, über den Namen zu finden und auseinanderzuhalten. Zum Beispiel
Code:
.DEF ms0		= R7		; Millisekunden * 10^0
.DEF ms1		= R8		; Millisekunden * 10^1
.DEF ms2		= R9		; Millisekunden * 10^2
.DEF Tab_Cnt	= R15		; Zähler
.DEF Byte_Cnt	= R16		; Zähler
.DEF Cnt_Reg	= R17		; Zähler
.DEF Work_Reg	= R22		; Zeiterfassung Std
.DEF Calc_1	= R23		; Rechenschritte
.DEF Calc_2	= R24		; Vergleiche
.DEF Send_Byte	= R25		; Sendewert
Etc.
Es folgen Wertezuweisungen mit symbolischem Namen
Code:
.equ F_CPU		= 16000000	; Systemtakt in Hz
Hier wird eine Constante mit dem Namen F_CPU mit dem Wert 16000000 belegt. Dies ist später von Bedeutung, da der Compiler als auch das Programm selber auf solche Werte über den Namen zugreifen kann. In der Formel, die der Compiler für die Berechnung der Baudrate benutzt, werden diese Werte verarbeitet.
Code:
.equ BAUD	= 38400		; Baudrate ; Berechnungen
.equ UBRR_VAL	= ((F_CPU+BAUD*8)/(BAUD*16)-1)  ; clever runden
.equ BAUD_REAL	= (F_CPU/(16*(UBRR_VAL+1)))      ; Reale Baudrate
.equ BAUD_ERROR	= ((BAUD_REAL*1000)/BAUD-1000)  ; Fehler in Promille 

;Makro zur Überprüfung
.if ((BAUD_ERROR>10) || (BAUD_ERROR<-10))       ; max. +/-10 Promille Fehler  
.error "Systematischer Fehler der Baudrate grösser 1 Prozent und damit zu hoch!"
.endif
Der Compiler prüft durch die Direktive „IF“ , ob das Ergebnis unter einem Schwellwert liegt und somit eine stabile Verbindung aufgebaut werden kann, oder ob die Fehler in der Rundung vom Ergebnis der Formel zu groß wird. Man beachte, das Ergebnis ist in Wirklichkeit keine Realzahl, sondern immer eine Ganzzahl, weil in einer Controlervariablen immer nur Byte oder Word abgelegt werden kann.

Merken wir uns : Eine Compilerdirektive arbeitet nicht auf dem Controller, sondern ist ein Makro innerhalb des Compilers.
 
Die Variablendeklaration und Adressierung

5.2 Die Variablendeklaration und Adressierung
Es folgen die Variablen im DSEG, d. h. Datensegment.
Das sind eigentlich gar keine Variablen im eigentlichen Sinne, sondern Sprungmarken, oder besser gesagt, Adressen.
Code:
.DSEG
Mein_Erster_Wert :  .Byte 1
bedeutet : Unter der Adresse Mein_Erster_Wert habe ich in einem Byte einen Wert abgelegt.
Code:
Mein_Erstes_Wort : .Byte10
bedeutet: Unter der Adresse Mein_Erstes_Wort habe ich 10 Byte für mehrere Werte
, z. B. ASCII – Zeichen.
Code:
Noch_ein_Wert:  .Byte 1

Hat dann die 12. Speicherstelle nach Mein_Erster_Wert.
Die Variablen bauen also im Datensegment von der niederen zur höheren Adresse. Dies ist wichtig zu wissen, denn es gibt noch einen Benutzer des SRams, den Stack, und der wächst von der hohen zur niederen Speicheradresse. Ich werde das in einem eigenen Abschnitt erklären.
Zurück zum Variablenbereich. Da ich weiß, das die Variablen aneinander im Speicher liegen, kann ich mir dieses Wissen auch zunutze machen. Angenommen, ich muß 4, 10 oder gar 20 zusammenhängende Werte an den PC denden, so kann ich dies natürlich mit einzelnen Load –Befehlen bewerkstelligen. Eleganter ist aber eine Schleife und ein Adressregister, welches auf die aktuelle Speicherzelle Zeigt. Da brauche ich die Namen gar nicht mehr aufzurufen. Lediglich die Anfangsadresse, z. B. von Mein _Erstes_Wort in eines der Doppelregister, und ich kann den Zugriff in einer Schleife berechnen lassen. Ein Doppelregister braucht es aus folgendem Grund:
Ein Byte kann 256 Speicherzellen adressieren. Da wir bei 0 anfangen ist der max. Wert immer 256-1. Unser SRam ist aber 1024 Bytes groß. Da ist ein wesentlich breiteres Adressiermuster notwendig. Mindestens 10 Bit werden für diesen Adressraum benötigt. Da es keine „Bytes“ mit 10 Bit gibt und die nächste Größe eines Bitbereichs ein Word ist , also 2 Byte müssen 2 Register zusammengeschaltet werden. Wie bereits erwähnt sind dies die Register R 26 bis R 31, die wir als Z, X und Y –Register nutzen können.

Analog zu dem Gesagten gilt das selbe für das EEProm. Die Zugriffe sind etwas anders, aber ich kann genauso gut über Variablennamen oder über Registeradressierung auf Werte zugreifen..
Hier ein Zugriff auf den SRam mit Register Z als Adressierung:
Code:
LDI ZH,high(Mein_ Erstes_Wort)	; Variablenadresse holen
LDI ZL,low(Mein_ Erstes_Wort)	 
LD  Work_Reg, Z			; Wert aus der Speicherzelle holen
Eine solche Adressierung ist sehr nützlich, da die Bearbeitung ganzer Speicherbereiche innerhalb von Schleifen durchführbar ist.
So wird es ermöglich, die Werte in den Variablen in einer Schleife als Block an den PC zu senden und dort zu visualisieren. Das Adressregister Z wird dabei in der Schleife erhöht, bis alle Zeichen gesendet sind und die Schleife verlassen werden kann. Für den Moment sollte die Information zu den Variablen erst einmal genügen.
 
Die Interrupt Vector Table

5.3 Die Interrupt Vector Table
Nun kommen wir in den Bereich des CSEG, also Codesegmentes.
Code:
; Programmbereich
.CSEG
.org 0000
Reset:	RJmp Start
Hier sagt die Compilerdirektrive .ORG das bei Adresse 0 im Codesegment das Programm beginnt und gleich einen Sprung zur Adresse (Marke) Start durchführen soll. Warum? Nun, die nächsten Adressen haben alle Sprungmarken und wenn ich da nichts eintrage, werden Interrupts in das Programm geleitet oder das Programm durchläuft die Interruptroutinen. Deshalb steht auch in der direkten Folge die Aufrufe der ISR (Interrupt Service Routinen) . Man nennt diesen Bereich auch Interrupt Vector Tabelle.
Code:
.org 	INT0addr 		; External Interrupt0 Vector Address 
	Reti
                   
.org 	INT1addr		; External Interrupt1 Vector Address       
Reti                   
.org 	OC2addr		; Output Compare2 Interrupt Vector Address       
Reti                   
.org 	OVF2addr		; Overflow2 Interrupt Vector Address       
Reti                   
.org 	ICP1addr		; Input Capture1 Interrupt Vector Address       
Reti                   
.org 	OC1Aaddr		; Einsprungadresse ISR Timer1
	RJmp 	isrTimer1                
.org 	OC01Aaddr		; Einsprungadresse ISR Timer0
	RJmp 	isrTimer0                
                 
.org 	OC1Baddr                 	; Output Compare1B Interrupt Vector Address       
Reti
Etc.
Wie wir erkennen, steht vor jedem Interruptnamen ein .Org. Dies ist nicht notwendig, da bereits diese Tabelle fest verdrahtet ist. Aber es dient der Übersichtlichkeit. Die IVT hat immer 2 Byte für jeden Interrupt. Werden diese ausgelöst, so springt der Controller an die dem Interrupt zugeordnete Adresse.
Setze ich an diese Stelle ein JMP <Marke>, dann wird der Controller veranlasst, sein Programm unter der angegebenen Marke fortzuführen. Für ein RETI ist dann aber kein Platz mehr in der Tabelle, daher wird dieses RETI an das Ende des Programmes gesetzt, was als Interrupt Service Routine arbeiten soll. Es ist sozusagen ein Unterprogramm, zu dem es keinen Call gibt.
Wir können uns also merken, wird ein Interrupt ausgelöst, holt sich der Controller seinen nächsten Befehl aus der Interupt Vector Tabelle. Entweder er findet dort einen Sprung zu einer ISR, oder einen RETI( Return from Interrupt). Wäre dort nichts eingetragen, so läuft er adressenmäßig weiter, bis er den Bereich hinter Start erreicht. Nun beginnt wieder eine Initialisierung und man wundert sich, das das Programm überhaupt nicht so läuft, wie erwünscht. Allein aus diesem Grund ist die vollständige Beschreibung der Interrupt Vector Tabelle empfehlenswert.
 
Initialisierungsabschnitt Der Stack

5.4 Initialisierungsabschnitt
Kommen wir zu unserer Programm- Einsprungsmarke „Start“.
Ein wesentlicher Bestandteil eines Programmes ist der Anlauf. Die Hardware ist für den Start eines Programmes bei Adresse 0 durch Auslösen eines Reset-Befehles zuständig. Das bedeutet aber nicht, das die Inhalte der Speicherstellen automatisch mit auf 0 geschrieben werden. Daher
durchlaufen wir verschiedene Initialisierungen, die der Controller machen muß. Hier muß darauf geachtet werden, das mit Startwerten gearbeitet wird und nicht mit irgend welchen zufälligen Bitmustern, wie sie nach dem Einschalten in den Speicherzellen vorzufinden sind.

5.5 Der Stack
Die absolut erste und unbedingt erforderliche Initialisierung gebührt dem Stack. Ohne ihn ist kein Unterprogramm lauffähig. Er wird sehr gern vergessen und dann wundert es den Autor, das da nix wirklich geht. Aus diesem Grund gehe ich etwas näher auf den Stack ein
Beginnen wir zuerst einmal damit, die Stack-Adresse festzulegen und in den Stackpointer einzutragen. Dies ist zwingend erforderlich und wir werden auch erkennen, warum hier kein Unterprogramm Init_Stack aufgerufen werden darf. Er dient dazu, Rücksprungadressen oder Registerwerte aufzunehmen und muß im DSEG liegen., da er laufend beschrieben wird. Praktisch bei jedem Programmdurchlauf bei jedem Call oder Push. Die Variablen bauen sich von der niedrigen Adresse an aufwärts auf, während der Stack entgegengesetzt läuft. Deshalb wird auch die höchste Adresse ermittelt und der Stackzeiger in der Initialisierung dorthin gesetzt. Dieser Vorgang darf nur 1 mal beim Start des Programmes erfolgen.
Code:
LDI	R26, high(RAMEND)	; Stack Pointer setzen 
OUT	SPH, R26		; in m8def.inc def.
LDI	R27, low(RAMEND)	; 
OUT	SPL, R27
Ich habe hier mal schematisch ein Abbild des Speichers dargestellt. Es ist nicht vollständig, aber wir wollen hier ja auch nur den Stack und das Programm betrachten.
Wie wir wissen, ist nur das DSEG in der Lage, ständige Schreibzugriffe wegzustecken, ohne dabei sich zu zerstören.
Wie ist nun dieses Datensegment einzuteilen, denn offenbar dort auch die Variablen enthalten. Nun, ein paar Variablen können wir schon vergeben, aber wenn das Datensegment eine Speichergröße von 1 KB hat, können wir nicht den gesamten Bereich für variable Werte nutzen. Es ist auch schwierig, den genauen Stackbedarf zu ermitteln. Aber grob schätzen kann man schon und ich denke, ein Stackbereich zwischen 30 und 40 % vom Datensegment ist ausreichend.
Da wir zur Adressierung nicht mit einem Byte auskommen, d. H. der Speicher größer 256 Bytes ist, brauchen wir das High- und das Low-Byte der Adresse. Diese Bytes werden in S(tack)P(ointer)H(igh) und S(tack)P(ointer)L(ow) eingetragen. SP ist ein 16 Bit-Register, welches wie auch das Statusregister SREG im IO-Bereich liegt und mit IN- und OUT-Befehlen angesprochen wird.
Der Aufruf eines Unterprogramms darf erst nach dieser Initialisierung erfolgen.
 

Anhänge

  • Stack.jpg
    Stack.jpg
    20,6 KB · Aufrufe: 25
Initialisierung durch Unterprogramme

5.6 Initialisierung durch Unterprogramme
Nachdem wir unseren Stack definiert haben, sind wir in der Lage, Unterprogramme mit einem Call aufzurufen. Das ist an dieser Stelle sehr nützlich, besteht ja auch im laufenden Programm der Wunsch, auf Befehl hin einige Werte zurückzusetzen.
Allerdings gibt es auch Initialisierungen, die nur einmal durchgeführt werden, doch auch für diese werden wir ein eigenes Unterprogramm schreiben. Es ist wartungsfreundlicher und hilft, die Initialisierungsroutinen einfacher zu pflegen. Da ist z. B. die Routine zur Initialisierung des Timers. Diese ist bereits wie auch die vom UART anfangs beschrieben, so das ich hier nicht wiederholen möchte.
Neu dazu kommt die Festlegung der Ports. Also die Initialisierung der IO-Ebene. Dann müssen wir unseren Registern und Variablen Anfangswerte verpassen. Es sollte immer im Hinterkopf sein, eine Elektronic, gleich welcher Art, hat nach dem Einschalten nicht unbedingt immer den gleichen Status am Ausgang. Speziell Speicherbereiche sind rein willkürlich mit Einsen und Nullen gefüllt. Daher wird eine Startwert-Initialisierung durchgeführt.
So wird also der Timer und der Uart in einem Unterprogramm initialisiert. Danach folgt das Vorbesetzen der Register mit Anfangswerten. Auch einige Variablen müssen auf Startwerte gesetzt werden, man denke da an die Runden- und Zeitzähler. Da diese auch im laufenden Programm zurückgesetzt werden müssen, wird dies in Unterprogramme gepackt.
Dementsprechend ist auch der Programmabschnitt einfach gehalten
Code:
Start:
LDI	R26, high(RAMEND)	; Stack Pointer setzen 
OUT	SPH, R26		; "RAMEND" ist in m8def.inc festgelegt
LDI	R27, low(RAMEND)	; 
OUT	SPL, R27      
RCALL	Init_Timer1		; nur einmal benötigte Initialisierung zuerst
RCALL	INIT_UART
RCALL	INIT_Register		; erst danach die Programm-Reset Routinen
RCALL	INIT_ZeitZaehler
RCALL	INIT_Werte
RCALL	INIT_Parameter
	;……	; alle hier gelisteten Routinen werden beim Programmstart einmal durchlaufen


Erst nach dieser Startsequenz wird die eigentliche Programmschleife eingerichtet.
 
Programmschleife

5.7 Programmschleife
Auch wenn wir bisher schon Code geschrieben und auch schon einiges ausgeführt haben, so beginnt unser Programm erst wirklich in einer Schleife, die praktisch unendlich durchlaufen wird. Dies überlassen wir aber nicht dem Controller, sondern wir weisen ihm die eigentliche Programmschleife zu. Er soll ja nicht wieder die Initialisierung durchlaufen. Ab jetzt soll er mit den Werten arbeiten, die sich ständig verändern. Daher sagen wir ihm , hier beginnt deine Programmschleife.
Code:
Loop:
Und weil es so schön einfach ist, sagen wir auch, was er nach der Marke Loop machen soll.
Code:
RJMP	Loop	; Springe zur Marke Loop.
Ab jetzt wird alles, was sich innerhalb dieser Schleife an Befehlen befindet, immer und immer wieder bearbeitet.
Wir lassen unser Programm nun ständig wachsen, indem wir uns Routinen einfallen lassen, die hier aufgerufen werden. Wir halten uns an die alte Regel „EVA“. Also beginnt man mit einem „E“ wie Eingabe.
Also ein
Code:
RCALL	Read_IO	; Lese die Eingangskanäle
RCALL	Read_UART	; Lese serielles Interface

Nun erfolgen alle Aufrufe von V für Verarbeitung. Also Auswerten, entscheiden, setzen von Werten etc.
Letztlich folgt das A wie Ausgabe
Code:
RCALL	Write_IO	; setzen der Ausgabekanäle
RCALL	Write_UART	; schreibe serielles Interface

Unsere Schleife sieht nun wie folgt aus:
Code:
Loop:
	RCALL	Read_IO	; Lese die Eingangskanäle
             RCALL	Read_UART	; Lese serielles Interface
	RCALL	Verarbeitung         ; Aufruf der Bearbeitungsroutinen 
	RCALL	Write_UART	; schreibe serielles Interface
	RCALL	Write_IO  	; setzen der Ausgabekanäle

RJMP	Loop	; Springe zur Marke Loop.

Ein Hinweis, der uns in den Programmeditoren immer wieder nützlich ist, Code einrücken. Dadurch werden Codeblöcke leichter lesbar.
 
Unterprogramme

5.8 Unterprogramme
Der Aufbau vom Hauptprogramm zeigt, das eigentlich gar nicht soviel Programm in einem Abschnitt stehen muß. Dadurch bleibt der Abschnitt übersichtlich und was noch ganz wichtig ist, Das Blackbox-Verfahren ist anwendbar. In dem Codebeispiel der Programmschleife befinden sich die Aufrufe verschiedener Unterprogramme. Da diese noch nicht geschrieben sind, wird der Compiler eine Fehlermeldung bringen. Dem wird nun die Blackbox vorgeschoben und der Compiler kann seinen Dienst versehen, ohne das ein Fehler erkannt wird. Gleichzeitig wird im Text durch großzügige Kommentarzeilen die Aufgabe der Routine beschrieben du die Information zur Rückgabe der Werte eingetragen. Der fertige Code wird dadurch nicht größer.
Code:
; *******************************************
; *  Dieser Abschnitt liest Port B sowie Port C ein            *
; *  und schreibt die Werte in die Variablen                     *
; *  Eingang_A   und Eingang_B                                    *
; *  *****************************************
Read_IO:	; Noch ist es eine Blackbox

RET
Dieser Code ist so zu nichts nütze, aber er soll ja so auch nicht bleiben.
Schließlich werden sehr viele solcher Konstrukte benutzt, um erst einmal die Struktur grob abzubilden. Die Detailarbeit wird weitere solcher Konstrukte fordern und bis ein Programm perfekt läuft, werden manche Stunden Schreibarbeit vergehen. Dennoch möchte ich hier explizit darauf hinweisen, eine solche Vorgehensweise wird auch bei anderen Programmiersprachen angewendet.
Analog dazu werden die Routinen für die Ausgabe aufgebaut:
Code:
; *******************************************
; *  Dieser Abschnitt schreibt die aufbereiteten               *
; *  aus der Variablen Ausgabebyte in den Port_B            *
; *  *****************************************
Write_IO:	; Noch ist es eine Blackbox
	In	S_Merker, PortB		; alten Zustand lesen
	ANDI	S_Merker, 0b11000000	; alle Bits bis auf Bit 6 und 7 löschen
	LDS	T_Merker, Ausgabebyte
	ANDI	T_Merker, 0b00111111	; Ausgabebyte aufbereitet
	OR	T_Merker, S_Merker	; gesetzte Bits einfügen
	Out	PortB, 	T_Merker		; Bit 6 und 7 sind unverändert
RET
Diese Technik wird an Stellen eingesetzt, wo die Lösung einer Aufgabe noch nicht erkennbar ist oder weiteres Wissen erfordert.
 
Status
Für weitere Antworten geschlossen.

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)