Keine Angst vor Assembler

Status
Für weitere Antworten geschlossen.
Interrupt (Ereignisserfassung) Der Timer

5.9 Interrupt (Ereignisserfassung)
Ein Interruptprogramm ist einem normalen Unterprogramm sehr ähnlich, allerdings gibt es hier einen ganz wichtigen Punkt zu beachten:
Ein Interrupt kann an jeder ! Stelle im Programm auftreten und das Unterprogramm aufrufen.
Warum ist dies so wichtig ? Nun, bei einem normalen Unterprogramm weiß ich, wann der Aufruf erfolgt und welche Register ich benutzen kann, ohne das dabei eine Information verloren geht. Ein Interrupt löst aus, irgendwo. Mir ist nicht bekannt, welches Register gerade irgendwo bearbeitet wird oder einen wichtigen Wert beinhaltet. Daher ist es erforderlich, jedes in der ISR benutzte Register zu retten.
Wie wird das gemacht ? ganz einfach, in dem man sich den Stack zu Hilfe nimmt. Befehle wie Push und POP sind dafür ausgelegt, Registerwerte auf dem Stack zu sichern. Schauen wir uns einmal eine typische ISR an:

5.10 Der Timer
Code:
; ********************************************************
; * Der Timerinterrupt erzeugt einen Impuls  alle ms. Dieser wird für die       *
; * Zeitmessung zugrunde gelegt. Folgende Register werden benutzt:         *
; ********************************************************

isrTimer1:				; Sprungmarke aus der Interrupt Vector Tabelle
	Push	Temp			; Register 1 sichern
	In	Temp, SREG		; Statusregister holen
	Push	Temp			; und auf Stack sichern
	Push	S_Merker		; 2. register sichern
	;…..			; hier ist das eigentliche Programm
	;…..
	Pop	S_Merker		; Register in umgekehrter Reihenfolge aktualisieren
	POP	Temp		; Statusregister 
	Out	SREG, Temp	; zurückschreiben
	POP	Temp		; Register 1 aktualisieren
RETI
Es gilt die Reihenfolge zu beachten. First In ist Last Out, daher sagt man auch zum Stack, er ist ein FILO-Speicher (First In – Last Out)

Meine Empfehlung ist daher, bevor eine ISR mit Code gefüllt wird, die Push – und Pop –Befehle zu platzieren. Noch ist der Zusammenhang erkennbar. Die eigentliche Bearbeitung setze ich dann in den Bereich ein, wo jetzt noch Punkte sind.
Hinweis: Unterprogramme, die aus einer ISR aufgerufen werden, müssen ebenfalls verwendete Register sichern. Dies kann hier gleich mit berücksichtigt werden.
Der Timer – Interrupt erledigt alle Zeitverzögerungen dur Zählen der ms und entsprechender Auswertung. So sind z. B. Register definiert, die einen Zeitbereich abdecken. So kann aus diesen Registerwerten jedes Mal bei Erreichen einer Vorgabe ein Flag gesetzt werden, völlig unabhängig vom Programm. Im Hauptprogramm wird dieses erkannt und bearbeitet.
 
Verzögerung mit Timer und Flags

5.11 Verzögerung mit Timer und Flags (Fortsetzung folgt)
Wir haben in unserem Programm die Variable Timer_Flag definiert und die einzelnen Bits beschrieben.
Code:
Timer_Flag:		.Byte 1		; Bit 0 = 1ms
					; Bit 1 = 10 ms
					; Bit 3 = 100 ms
					; Bit 4 = Sekunde
					; Bit 5 = Minute
					; Bit 6 = Stunde
					; Bit 7 = Tag
Nun die ISR Timer:
Code:
isrTimer1:		; Sprungmarke aus der Interrupt Vector Tabelle
	Push	Temp		; Register 1 sichern
	In	Temp, SREG	; Statusregister holen
	Push	Temp		; und auf Stack sichern
	Push	S_Merker		; 2. register sichern
	LDS	Merker, Timer_Flag
	ORI	Merker, 0b00000001
	STS	Timer_Flag , Merker
	Inc	ms0
	CPI	ms0, 10		
	BRLO End_ISR_Timer
	CLR	ms0
	LDS	Merker, Timer_Flag
	ORI	Merker, 0b00000001
	STS	Timer_Flag , Merker
	INC	ms1
	CPI	ms1, 10		
	BRLO End_ISR_Timer
	CLR	ms1
	LDS	Merker, Timer_Flag
	ORI	Merker, 0b000001001
	STS	Timer_Flag , Merker
	INC	ms2
; usw …  der Ablauf ist praktisch immer gleich, lediglich die Vergleichswerte ändern sich
End_ISR_Timer:
	Pop	S_Merker		; Register in umgekehrter Reihenfolge aktualisieren
	POP	Temp		; Statusregister 
	Out	SREG, Temp	; zurückschreiben
	POP	Temp		; Register 1 aktualisieren
RETI
Im Hauptprogramm wird nun nach jedem Schleifen durchlauf nachgesehen, ob eines der Flags gesetzt ist und eine entsprechende Routine aufgerufen. Nach Bearbeitung wird das Bit gelöscht. So ist jede denkbare Zeit möglich, ohne mit Delays und ähnlichen Programmierungen die Laufzeit des Programms zu beeinflussen
 
Der UART – Interrupt

5.12 Der UART – Interrupt
Ein weiterer Interrupt wird durch den UART ausgelöst, wenn er ein Zeichen empfangen hat.
Auch hier haben wir keinen Aufruf vom Programm sondern aus der Interrupt Vector Tabelle. Das empfangene Byte soll in einem Ringbuffer eingetragen werden und dort vom Programm abgeholt und bearbeitet werden. Dazu brauche ich ein paar Variablenvereinbarungen
Code:
Ring_Buf:	.Byte 20
Write_Pos:	.Byte 1		; Zeiger für die Schreibposition im Ringspeicher
Read_Pos:	.Byte 1		; Zeiger für die Leseposition im Ringspeicher
Nun kann die ISR geschrieben werden.
Code:
; *******************************************
; *  der UART Interrupt wird beim Empfang                      *
; *  ausgelöst. Der empfangene Wert wird in den              *
; * Ringbuffer eingetragen.                                           *
; *                                                                           *
; *  *****************************************

Int_RXc:	PUSH	Temp				; temp auf dem Stack sichern
		PUSH	ZH
		PUSH	ZL
		IN	Temp, SREG		; SREG sichern    
		PUSH	Temp
		LDS	Temp, Write_Pos		; Write_Pos = Schreibzeiger Ringpuffer
		LDI	ZH,High(Ring_Buf)
		LDI	ZL,Low(Ring_Buf)		; Adresszeiger auf Ringbuffer
		INC	Temp				; Schreibzeiger erhöhen
		CPI	Temp,	20		; Schreibzeiger Grenze ?
		BRLO	Eintrag			; unter Grenze, dann Empfang eintragen
		CLR	Temp				; Temp auf 0 setzen
Eintrag:	ADD	ZL, Temp			; Adresse kann größer als 1 Byte werden 
		ADC	ZH, 0				;daher Addition mit Übertrag
		IN	Work_Reg, UDR		; UART Daten lesen, Work_Reg ist Reserviert
		ST	Z, Work_Reg		;Empfang aus UART  in Puffer schreiben
		
	
		POP	temp 
		OUT	sreg, temp			; SREG wiederherstellen
		POP	ZL
		POP	ZH
		POP	temp				; temp wiederherstellen    
RETI

Wir haben nun die wichtigsten Elemente besprochen und erklärt. Damit können wir nun beginnen, unser eigenes Programm zu schreiben.
 
Das Projekt

6. Das Projekt
6.1 Gliederung

Bevor wir beginnen, sollten wir einmal grob erfassen, was unser Programm alles können muß. Dazu ist es nicht verkehrt, erst einmal Block und Bleistift zu nehmen und das Programm in einer Skizze zu entwerfen, ähnlich wie es Eingangs erwähnt wurde, doch nun etwas detailierter.
Die Struktur ist ja schon klar und ein Blockdiagramm sagt ein wenig mehr aus:
Also,
ein Block für die Initialisierung
ein Block für den Timer-Interrupt
ein Block für den UART-Interrupt
ein Block für die Eingaben
hier beginnt die Signalverarbeitung

Ein Block Signalerkennung Änderung – Ergebnis 1 Byte
Ein Block Signalerkennung High-> Low – Ergebnis 1 Byte
Ein Block Signalerkennung Low->High – Ergebnis 1 Byte
Ein Block Auswertung Empfang, prüfen Ring_Buffer - Ergebnis in Work_Reg
Ein Block Auswertung Empfang, prüfen Befehl - Ergebnis in Flagregister Auftrag
Ein Block Auswertung Empfang, bearbeiten Befehl - Ergebnis in Flagregister Auftrag
Ein Block Ausgaben aufbereiten – Ergebnis 1 Byte
Ein Block Zeitenzähler – Verzögerungen Ergebnis ein Bit
Ein Block Flags bearbeiten – Ergebnis verschieden

Ein Block Daten versenden - gehört schon zu den Ausgaben
ein Block für die Ausgaben

Nun versuchen wir den Blöcken soviel Information wie möglich mitzugeben, einen Ansatz haben wir ja schon durch die Bestimmung der Ergebnisse.
Sollte es sich für zweckmäßig ergeben, weitere Aufsplittung der Blöcke durchzuführen, so wird ein weiteres Blatt genommen und der Block aufgedröselt, wie auch das Hauptprogramm. Manche Blöcke sind auch vom Ergebnis eines anderen Abhängig, also werden sie in diese Blöcke eingegliedert. Ein Beispiel ist Signalerkennung Änderung. Wenn hier nichts vorliegt, brauchen wir auch nicht nachsehen, ob ein Wechsel von High-> Low oder Low->High stattgefunden hat. Deshalb schauen wir diesem Block mal hinter die Kulissen und setzen das Programm um.
 
Signalerkennung

6.2 Signalerkennung
Beginnen wir mit der Erkennung eines Signalwechsels. Dazu müssen wir nur ein abgelegtes Byte mit einem neu gelesenen Byte vergleichen. Diesen Wert finden wir nach unserer Leseroutine in der Variablen „Neu_Input.
Code:
; ****************************************************
; *  Lesen der Eingänge Port C Bit 0 und 1 sowie Port D Bit 2 -           *
; *  Andere Bits werden ausgeblende und die beiden Register             *
; *  zusammengeführt. Das Ergebnis  wird in die Variable   Neu_Input   *
; *eingetragen.                                                                          *
; * ***************************************************
Read_IO:	In S_Merker, PortC		; Port C in Register S_Merker schreiben
		;Nun blenden wir unerwünschte Bits aus
		ANDI	S_Merker, 0b00000011	; Nur Bit 0-1 sind für uns interessant.
		In 	T_Merker, PortD		; Port D in Register T_Merker schreiben
		;Nun blenden wir unerwünschte Bits aus
		ANDI	S_Merker, 0b11111100	; Nur Bit 0-1 sind für uns interessant.
		OR 	S_Merker, T_Merker	; führen die Eingänge zusammen
		STS	Neu_Input_A,S_Merker	; und legen neuen Wert in Variable ab
RET
Da für die notwendige Boolsche Operation 2 Register benötigt werden laden wir erst einmal Register S_Merker mit der neuen Information vom Port.
Code:
; *******************************************
; *  Es wird geprüft, ob eine Änderung eines                   *
; *  Einganges aufgetreten ist und das Ergebnis               *
; *  in Die Variable IO_Change_Bit eingetragen .               *
; *  Im Programm wird dieses Bit nach der                       *
; * Bearbeitung gelöscht                                             *
; *  *****************************************
Chk_IO_Change:
	LDS 	S_Merker, Neu_Input_A		; Reg. S_Merker mit neuem Wert laden
	LDS	T_Merker, old_Input_A		; das letzte gelesene Byte holen
	EOR	T_Merker, S_Merker		; S_Merker ist nicht verändert, 
	BREQ End_Chk_Change			; Reg. T_Merker ist 0 , keine Änderung
	STS	IO_Change_Bit_A, T_Merker	; Bits ablegen, die sich geändert haben
	RCall	Chk _ToLow			; Aufruf der Prüfung Wechsel von 1 nach 0
	RCall	Chk _to_High			; Aufruf der Prüfung Wechsel von 0 nach 1
	STS	Old_Input_A, S_Merker		; Unterprogramme dürfen S_Merker nicht verändern
End_Chk_Change: 
RET
Die Bits in IO_Change_Bit sind geeignet, eine Ereignisbearbeitung durchzuführen. Allerdings nur in diesem Zyklus. Beim nächsten Durchlauf werden sie neu bewertet.
 
Signalauswertung

6.3 Signalauswertung

Nun, dieses kleine Programm ist doch schön übersichtlich.
Schauen wir uns die Flankenauswertung an:
Code:
; *********************************************************
; *  Es wird geprüft, ob die Änderung von 1 nach 0  erfolgt ist und das Ergebnis     *
; *  in die Variable  Change_To_Low eingetragen.  Im Programm wird dieses  Bit  *
; *  nach der Bearbeitung gelöscht und löst nur eine einmalige  Bearbeitung   aus.   *
; *  ********************************************************************
Chk_To_Low:
	Push	S_Merker			; diese Registerinhalte werden noch gebraucht
	Push	T_Merker
	LDS	S_Merker, Old_Input_A	; Eine alte 1 und die Änderung ergeben eine 1
 	And	T_Merker, S_Merker	
	LDS	S_Merker, Change_To_Low_A	; nur Änderungen eintragen, andere Bits
	Or	T_Merker, S_Merker		; müssen ihren Wert erhalten
	STS	Change_To_Low_A, T_Merker
	POP	T_Merker
	POP	S_Merker
RET		

; **********************************************************************
; *  Es wird geprüft, ob die Änderung von 0 nach 1  erfolgt ist und das Ergebnis      *
; *  in die Variable Change_To_ High eingetragen.  Im Programm wird dieses  Bit  *
; *  nach der Bearbeitung gelöscht und löst nur eine einmalige  Bearbeitung   aus.   *
; *  ********************************************************************
Chk_To High:
	Push	S_Merker			; diese Registerinhalte werden noch gebraucht
	Push	T_Merker
	LDS	S_Merker, neu_Input_A	; Eine neue 1 und die Änderung ergeben eine 1
 	And	T_Merker, S_Merker	
	LDS	S_Merker, Change_To_High_A	; nur Änderungen eintragen, andere Bits
	Or	T_Merker, S_Merker		; müssen ihren Wert erhalten
	STS	Change_To_High_A, T_Merker
	POP	T_Merker
	POP	S_Merker
RET
Durch die Flankenauswertung erreichen wir, das nicht ständig auf einen erkannten Wechsel reagiert wird. Betrachten wir einmal eine Zuweisung an einen Ausgang. Wir möchten, das Ein Tastendruck einen Ausgang setzt und beim loslassen dieser Ausgang wieder ausgeht. Arbeiten wir nach dem Prinzip: Wenn Eingang, dann Ausgang, dann müssen wir bei jedem Durchlauf diese Bedingung abfragen. Arbeiten wir mit Flanken, setzen oder löschen wir das Bit nur bei Änderung. Ist keine Änderung vorhanden, brauchen wir auch gar nicht weitermachen. Die folgenden Codeschnipsel werden nur bei gesetzten Ereignisflags durchlaufen
Code:
LDS	S_Merker,  AusgabeByte		; Variablenablage Ausgabebits
LDI	T_Merker, Change_To_High_A
ANDI	T_Merker, 0b00000001		; nur Bit 0 wechseln übernehmen
OR	S_Merker, T_Merker,		; Wechsel von 0 – 1
STS	AusgabeByte, S_Merker		; für die Ausgaberoutine ablegen
LDS	S_Merker,  Change_To_High_A	; Flankenbit löschen
ANDI	S_Merker, 0b11111110		; nur letztes bit auf 0 ändern
STS	Change_To_High_A, S_Merker	; und wieder ablegen
Und hier den Ausgang löschen
Code:
LDS	S_Merker,  AusgabeByte		; Variablenablage Ausgabebits
LDI	T_Merker, Change_To_Low_A
ANDI	T_Merker, 0b00000001		; nur Bit 0 wechseln übernehmen
BREQ	next...
ANDI	S_Merker, 0b11111110		; Bit 0 auf 0, andere bleiben im alten Status
STS	AusgabeByte, S_Merker		; für die Ausgaberoutine ablegen
LDS	S_Merker,  Change_To_Low_A	; Flankenbit löschen
ANDI	S_Merker, 0b11111110		; nur letztes bit auf 0 ändern
STS	Change_To_Low_A, S_Merker	; und wieder ablegen
Die Flankenauswertung bringt noch weitere Vorteile. Möchte ich den Ausgang ein- und austasten mit dem selben Eingang, brauche ich bei jedem positiven oder negativen Flankenwechsel nur eine Exclusiv-Oder Verknüpfung mit dem Ausgangsbit und einer konstanten „1“.
Code:
	LDS	S_Merker,  AusgabeByte		; Variablenablage Ausgabebits
	LDI	T_Merker, 0b00000001		; nur Bit 0 wechseln
	EOR	T_Merker, S_Merker		; Wechsel von 1 – 0 – 1 – 0 – 1 usw.
							; bei jeder positiven  Flanke
	STS	AusgabeByte, T_Merker		; für die Ausgaberoutine ablegen
	LDS	S_Merker,  Change_To_High_A	; Flankenbit löschen
	ANDI	S_Merker, 0b11111110		; nur letztes bit auf 0 ändern
	STS	Change_To_High_A, S_Merker	; und wieder ablegen
Sollen Impulse gezählt werden, es ist fast die selbe Vorgehensweise
Code:
	LDS	S_Merker,  ZaehlByte		; Variablenablage Impulszähler
	INC	S_Merker				; erhöhe Wert bei jeder positiven  Flanke
	STS	ZaehlByte, S_Merker		; für die Ausgaberoutine ablegen
	LDS	S_Merker,  Change_To_High_A	; Flankenbit löschen
	ANDI	S_Merker,0b11111110		; nur letztes Bit auf 0 ändern
	STS	Change_To_High_A, S_Merker	; und wieder ablegen
 
Bitmanipulation

6.4 Bitmanipulation

Obwohl der Assembler über mächtige Befehle verfügt, die einzelne Zuweisungen an Bits ermöglichen, möchte ich hier einmal auf die binäre Logik zurückgreifen.
Beginnen wir zuerst mit der Maskierung. Das Bit 3 in einem Byte soll auf Zustand 1 abgefragt werden, um in ein Unterprogramm zu verzweigen
Zuerst laden wir das Byte und danach die Maskierung. Natürlich braucht diese Reihenfolge nicht eingehalten werden, aber es ist sinnvoll, für sich ein Schema beizubehalten.
Code:
LDS		Temp, Test_Byte
LDI		Merker, 0b00001000	; Bit 3 zur Abfrage ausmaskieren.
AND		Merker, Temp	; Temp unverändert, Merker Bit 3 hat 0 oder 1
BREQ		Next_Test
RCALL	Flag_three		; Bit 3 Aufgabe bearbeiten
Next_Test:
LDI		Merker, 0b00010000	; Bit 4 ausmaskieren.
Etc.
Nun möchten wir dieses Flag löschen, weil die Bearbeitung stattgefunden hat und eine weitere erst bei erneuten gesetzten Bit erforderlich ist. Auch hier brauchen wir eine Maske.
Code:
LDS		Temp, Test_Byte
ANDI		Temp, 0b11110111	; Bit 3 zum löschen ausmaskieren.
STS		Test_Byte, Temp	; nur Bit 3 ist gelöscht
Über die Und-Verknüpfung kann ich einzelne Bits selektieren oder löschen. Sollen aber einzelne Bits gesetzt werden, so ist die Oder-Verknüpfung zu verwenden.
Code:
LDS		Temp, Test_Byte
ORI		Temp, 0b00001000	; nur Bit 3 zufügen.
STS		Test_Byte, Temp	;
Alle anderen Bits werden nicht verändert. Logische Verknüpfungen setzen auch die Statusflags im Statusregister. Daher ist eine Auswertung, ob das Ergebnis 0 oder <> 0 ist, möglich, mit entsprechendem Sprungbefehl auch verwendbar. Eine Oder-Verknüpfung ist für diesen Zweck im Gegensatz zur Und-Maskierung nicht wirklich nützlich.
Kommen wir noch zur Frage, wann brauche ich ein zweites Register und wann genügt die direkte Zuweisung über ANDI und ORI ? Nun, möchte ich das Ursprungsbyte nicht verändern und weitere Test’s durchführen, macht die Verwendung eines zweiten Registers Sinn. Unumgänglich ist ein zweites Register bei der Exclusiv Oder (EOR- Anweisung) . Diese ist nur zwischen zwei Registern möglich.
Merken wir uns:
AND : beide Bits an einer Stelle eine 1 haben müssen, um eine 1 zu erhalten.
OR : mindestens ein einer Stelle Bit eine 1 besitzen muß, um eine 1 zu erhalten
EOR : beide Bits einer Stelle unterschiedlich sein müssen, um eine 1 zu erhalten.
 
Empfang auswerten

6.5 Empfang auswerten

Den Empfang eines Wertes über die serielle Schnittstelle haben wir ja mit der ISR bereits erledigt. Nun wollen wir aber die Werte nicht einfach in einen Ringpuffer schreiben, sondern auch damit etwas bewirken. Z.B. die Ampelstartsequenz auslösen oder Werte abfragen, eine Spur abschalten oder die Hupe ansteuern. Schauen wir erst einmal, wie wir mitbekommen, ob etwas eingetroffen ist. Dies wird im normalen Programmablauf eingebaut. Möglich ist ein solches Vorgehen auch innerhalb einer ISR, aber das würde sie sehr groß werden lassen. Außerdem können wir jetzt ruhig darangehen, eingetroffene Werte zuzuordnen.
Zuerst wird geprüft, ob die Zeiger Write_Pos und Read_Pos gleich sind. Ist dies der Fall, ist keine Information über die Schnittstelle an den Controller geschickt worden.
Man kann darüber streiten, ob die Abfrage vor den Aufruf der Empfangsprüfung gestellt werden soll, oder ob ich der Übersichtlichkeit wegen, aus dem Hauptprogramm nur Unterprogramme aufrufe. Wer Geschwindigkeit braucht, der setzt die Prüfung vor den Aufruf der Unterprogramme. Er spart pro Zyklus ein Call und ein Ret.
Ich bevorzuge an dieser Stelle die Übersichtlichkeit, daher rufe ich nur Unterprogramme auf und prüfe innerhalb der Unterprogramme.
Code:
; *********************************************
; *  Es wird geprüft, ob Lese-und Schreibzeiger                  *
; *  unterschiedliche Werte haben. Dies bedeutet,              *
; *  ein neuer Befehl oder Wert ist empfangen                    *
; *  worden. Dieser Wert wird in das Arbeitsregister             *
; *  übertragen und zu einem nachfolgenden                      *
; * Unterprogramm weitergeleitet                                     *
; *  *******************************************

Chk_RS232_Empf:
	LDS	Temp, Read_Pos		; Lesezeiger holen
	LDS	Merker, Write_Pos	; Schreibzeiger holen
	CP	Temp, Merker		; vergleichen und wenn 
	BREQ Chk_RS232_Empf_End	; gleich, dann Ende
	LDS	Temp, Read_Pos		; Read_Pos = Lesezeiger Ringpuffer
	LDI	ZH,High(Ring_Buf)
	LDI	ZL,Low(Ring_Buf)		; Adresszeiger auf Ringbuffer
	Inc	Temp				; Lesezeiger erhöhen
	CPI	Temp,	20		; Lesezeiger Grenze ?
	BRLO	Eintrag			; unter Grenze, dann Empfang ins Arbeitsregister
	CLR	Temp				; Temp auf 0 setzen
Chk_Work:	
	ADD	ZL, Temp			; Adresse kann größer als 1 Byte werden 
	ADC	ZH, 0				; daher Addition mit Übertrag
	LD	Work_Reg, Z		; nun ist Workreg neu beschrieben
	RCALL Chk_RS232_Bef		; und der Befehl kann ausgewertet werden
Chk_RS232_Empf_End:
RET
 
Prüfen auf Mehrbyte-Befehle - Blockempfang

6.5.1 Prüfen auf Mehrbyte-Befehle - Blockempfang
Nun steht in dem Arbeitsregister der übergebene Wert aus dem Empfangspuffer. Bisher habe ich kein Flag gesetzt, welches den Empfang signalisiert und welches nach Abarbeitung gelöscht wird, so wie es in den IO-Routinen durchgeführt ist. Daher muß ich aus der Erkenntnis heraus, das die Lese- und Schreibzeiger unterschiedlich waren, den empfangenen Wert direkt verarbeiten.
Auch hier ist diese Verarbeitung nur notwendig, wenn ein Zeichen eingetroffen ist, und deshalb macht es auch Sinn, aus dem vorangegangenen Unterprogramm direkt die weiteren Schritte aufzurufen.
Code:
; ********************************************************************
; *  Es wird geprüft, ob Befehle bereits erkannt  wurden, die weitere Daten         *
; *  erforderlich machen.  Diese Information ist in der Variablen  Bef_Control    *
; *  abgelegt . Erst wenn klar ist, dies ist ein neuer Befehl,  wird er decodiert.      *
; *  Je nach Ergebnis ist auch  Bef_Control verändert                                             *
; *  ******************************************************************
Chk_RS232_Bef :
	LDS	Merker, Bef_Control	; Befehlscontrollregister laden
	ANDI	Merker, 0b00001000		; Bit für 3 Byte Befehl prüfen
	BREQ	Chk_ Two_Byte		; kein 3 Byte-Befehl, prüfe 2 Byte
	RCall 	Three_Block			; bearbeite 3 Byte-Befehl
	RJmp 	Chk_RS232_Bef_End		
Chk_ Two_Byte:
	LDS	Merker, Bef_Control	; Befehlscontrollregister laden
	ANDI	Merker, 0b00000100		; Bit für 3 Byte Befehl prüfen
	BREQ	Chk_ One_Byte		; kein 2 Byte-Befehl, prüfe Befehl
	RCall 	Two_Block			; Bearbeite 2 Byte Befehl
	RJmp 	Chk_RS232_Bef_End 
Chk_ One_Byte:
	RCall	Chk_Order
Chk_RS232_Bef_End : RET
Auch dieser Abschnitt ist klein und überschaubar. Wichtig ist nur, das zuerst nachgesehen wird, ob eventuell ein Mehrbyte Empfang vorliegt. Erst wenn klar ist, das noch kein Befehl vorliegt, der weitere Bytes erwartet, kann die Einzelbetrachtung vorgenommen werden.
Auch wenn das Register Work_Reg nirgends auftaucht, so ist dessen Inhalt immer noch mit dem empfangenen Byte aus dem Ringpuffer geladen. Wie geht’s jetzt weiter ? Klar, wir schreiben die anderen Unterprogramme
 
Empfang Datenblock

6.5.2 Empfang Datenblock
Code:
; ***********************************************************************
; *  Es wird geprüft, wie viele Bytes bearbeitet wurden. Der Zähler ist in der             *
; *  Variablen  Bef_Cnt abgelegt.  Dieser Wert hoch gezählt und dient gleichzeitig    *
; *  als Offset.  In Value_Dir ist der Kanal hinterlegt, für den  die Werte bestimmt   *
; *  sind.                                                                                                                                *
; *  *********************************************************************
Three_Block:
	LDS	Reg_x, Value_Dir		; Kanaladresse laden
	LDI	ZH,High(Kanal_0)
	LDI	ZL,Low(Kanal_0)		; Adresszeiger auf 1. Kanal
	ADD	ZL, Merker			; Adresse kann größer als 1 Byte werden 
	ADC	ZH, 0				; daher Addition mit Übertrag
	LDS	Merker, Bef_Cnt		; Bytenummer laden
	ADD	ZL, Merker			; Adresse kann größer als 1 Byte werden 
	ADC	ZH, 0				; daher Addition mit Übertrag
	ST	Z, Work_Reg			; Information im Kanalbereich ablegen
	INC	Merker			; Bytezähler erhöhen
	CPI	Merker, 3			; Abfrage, ob Bytezahl erreicht
	BRLO	Three_Block_End		; und beenden
	LDS	Merker, Bef_Control
	ANDI	Merker, 0b11110111		; Bit für 3 Bytebefehl löschen
	STS	Bef_Control, Merker	; und Controlflags zurückschreiben
	CLR	Merker			; Reg_x Löschen
Three_Block_End:
	STS	Bef_Cnt, Merker		; Bef_Cnt zurückschreiben 
RET
Die Auswerteroutine für 2 Byte Befehle könnte genau so aussehen, und beispielsweise die Kanalnummer übermitteln. Denkbar sind auch lange Parametersätze mit n-Bytes. Dadurch wird dieser Block nicht größer, lediglich der Eintrag in der Vergleichszeile ändert sich. Wer möchte, macht aus diesem Unterprogramm ein Multi_Bock. Wie, es ist ganz einfach nnur der Vergleichswert in einer Variablen übergeben werden. Vergleicht einmal folgende Programmzeilen:
 
Empfang Datenblock variabel

6.5.3 Empfang Datenblock variabel
Code:
; ************************************************************************
; *  Es wird geprüft, wie viele Bytes bearbeitet wurden. Die Vorgabezahl der zu          *
; * empfangenen Bytes liegt in Cnt_Soll.  Der Zähler ist in der  Variablen  Bef_Cnt    *
; *  abgelegt.  Dieser Wert wird  hoch gezählt und dient gleichzeitig als Offset             *
; *  In Value_Dir ist der Kanal hinterlegt, für den  die Werte bestimmt sind.                *
; *  **********************************************************************
Multi_Block:
	LDS	Merker, Value_Dir		; Kanaladresse laden
	LDI	ZH,High(Kanal_0)
	LDI	ZL,Low(Kanal_0)		; Adresszeiger auf 1. Kanal
	ADD	ZL, Merker			; Adresse kann größer als 1 Byte werden 
	ADC	ZH, 0				; daher Addition mit Übertrag
	LDS	Merker, Bef_Cnt		; Bytenummer laden
	ADD	ZL, Merker			; Adresse kann größer als 1 Byte werden 
	ADC	ZH, Zero				; daher Addition mit Übertrag
	ST	Z, Work_Reg			; Information im Kanalbereich ablegen
	INC	Merker			; Bytezähler erhöhen
	LDS 	Temp, Cnt_Soll		; Anzahl der erwarteten Bytes
	CP	Merker, Temp		; Abfrage, ob Bytezahl erreicht
	BRLO	Multi_Block_End		; und beenden
	LDS	Merker, Bef_Control
	ANDI	Merker,0b11110111		; Bit für 3 Bytebefehl löschen
	STS	Bef_Control, Merker	; und Controlflags zurückschreiben
	CLR	Merker			; Reg_x Löschen
	STS	Cnt_Soll, Merker		; Reg_x gleich zum Reset der Vorgabe nutzen
Multi_Block_End:
	STS	Bef_Cnt, Merker		; Bef_Cnt zurückschreiben 
RET
Obwohl diese Routine nur minimal größer ist wie der Vorgänger, kann ich durch Vorgabe bis zu 255 Bytes nun einen Variablenblock schreiben. Also werde ich Multiblock einmal testen und sowohl für Zweibyte-Befehle und Dreibyte-Befehle anwenden können … oder ?
Im Prinzip ja, aber ich muß beachten, auf welchen Bereich das Register Z zeigt. Da 2 Byte –Befehle vermutlich ganz woanders abgelegt werden, muß ich Z vor dem Aufruf von Multiblock besetzen. Dann kann ich unabhängig von Multiblock meine Werte an die richtige Stelle schreiben. Und genau diese Vorgehensweise muß gelernt werden, um richtig und effektiv zu programmieren.
Ach ja, den Sinn einer solchen Programmierung möchte ich euch auch nicht verschweigen: Angenommen, ihr habt Parametersätze, z. B. Texte oder Telefonnummern, die eingetragen werden müssen. Die schickt ihr natürlich per PC und programmiert nicht die Ganze Software des Controllers. Also braucht ihr ein Kommando: Da kommen noch Daten und ihr müsst wissen, wie viele es sind. Der Rest ist dann schon Routine…..
 
Status und Controlflags

6.6 Status und Controlflags
Immer wieder werden wir mit dem Begriff „Flag’s“ konfrontiert. Was steckt dahinter, warum dieser Aufriss.
Dazu zuerst einmal der Hintergrund. Manchmal ist es notwendig, bestimmte Dinge im Kopf zu behalten. Wir tun dies beispielsweise mit einem Einkaufszettel. Nach dem Einkauf werfen wir ihn weg. So etwa vergleichbar ist für den Controller ein Flag-Byte. Da steht drin, welche Arbeiten noch offen sind, was gerade in Arbeit ist und diverses andere Zeug, was der Controller nicht gleich schön der Reihe nach abarbeiten kann. Bei den Empfangsdaten wird’ s deutlich, die Bytes liegen nicht bei jedem Zyklus vor, da vergeht einiges an Zeit, bevor ein weiteres erkannt wird. Flaggen überbrücken diese Zeit und signalisieren: da ist noch nichts zu Ende. Erst wenn alles erfaßt ist, werden diese Bits gelöscht und durch Setzen weiterer Bits signalisiert, der nächste Schritt ist freigegeben. Das gute daran, der Code bleibt lesbar und die Routinen übersichtlich.
Einen weiteren Vorteil will ich euch auch noch mitgeben. Die Variablen sind relativ einfach an einen PC zu senden. Das bedeutet, mit geeigneten Programmen kann ich mir den Ablauf der Bearbeitung im Controller ansehen. Und das ist ein immenser Vorteil. Ein Programm blind einfach nur durch Nachverfolgen der Befehle zu kontrollieren, erfordert eine enorme Konzentration. Da wir aber lernen, Programme zu schreiben, die mit Controllern kommunizieren, wird es uns auch möglich sein, die Variablen einfach alle an den PC zu schicken. Nun soll er aber nicht bei jedem Zyklus ein Datenpaket versenden, sondern nur nach Aufforderung vom PC. Also müssen wir schon mal einen Befehl „erfinden“, der eine solche Aktion auslöst. Dazu reicht ein
1 –Byte Befehl. Legen wir einmal folgendes fest: ein Großbuchstabe übermittelt und ein Kleinbuchstabe fordert an.
Also wäre ein „V“ mindestens ein 2 Byte-Befehl, weil ja ein Wert kommt, der eventuell ausgegeben werden will. Ein „v“ allerdings fordert an. Aber warum V und v ? Nun, ich nehme gern schon mal den ersten Buchstaben und Value- bedeutet Wert. Wer’s anders mag, der kann auch W und w nehmen. Auch kann man Relais beispielsweise mit A einschalten und a ausschalten, B einschalten du b ausschalten, C usw.
Zurück zum Thema:
 
Auswertetabelle

6.7 Auswertetabelle
Code:
; **********************************************************************
; *  Es wird geprüft, welcher Befehl ausgeführt werden soll. Hier entscheidet sich,  *
; *  ob Ein-oder    mehrere Bytes zu diesem Befehl gehören.    Dazu wird der           *                       
; *  Inhalt von Work_Reg geprüft . Diese Liste kann beliebig erweitert werden        *
; *  ********************************************************************
Chk_Order:
	CPI	Work_Reg, ‚M‘		; Multibytebefehl ?
        BRNE CHK_Ag			; sonst weiter mit Testen auf „A“
        RCall Set_Multi_Byte		; Aufruf Unterprogramm
        RJMP Chk_Order_End		; 
CHK_Ag:
	CPI	Work_Reg, ‚A‘		; Relais A einschalten ?
        BRNE CHK_ak			; sonst weiter mit Testen auf „a“
        RCall Set_Relais_A		; Aufruf Unterprogramm
        RJMP Chk_Order_End		; 
CHK_ak:
        CPI	Work_Reg, ‚a‘		; Relais A ausschalten ?
        BRNE CHK_Bg			; sonst weiter mit Testen auf „B“
        RCall Set_Relais_A		; Aufruf Unterprogramm
        RJMP Chk_Order_End		; 
CHK_Bg:
	CPI	Work_Reg, ‚B‘		; Relais B einschalten ?
        BRNE CHK_bk			; sonst weiter mit Testen auf „b“
        RCall Set_Relais_B			; Aufruf Unterprogramm
        RJMP Chk_Order_End		; 
CHK_bk:
	CPI	Work_Reg, ‚b‘		; Relais B einschalten ?
        BRNE CHK_Vg			; sonst weiter mit Testen auf „V“
        RCall Set_Relais_B			; Aufruf Unterprogramm
        RJMP Chk_Order_End		; 
CHK_Vg: 
        CPI	Work_Reg, ‚V‘		; Werte empangen, z. B. Parameter ?
        BRNE CHK_vk			; sonst weiter mit Testen auf „R“
        RCall Set_Value			; Aufruf Unterprogramm
        RJMP Chk_Order_End		; 
CHK_vk:
	CPI	Work_Reg, ‚v‘		; Werte angefordert ?
        BRNE Chk_Order_End		; sonst ende
        RCall 	Send_Value			; Aufruf Unterprogramm
 Chk_Order_End:
Ret
Unser Programm hat nun seine eigene Sprungtabelle für Befehle, die der PC sendet.
 
Daten senden

6.8 Daten senden
Code:
; ******************************************************************
; * Senderoutine an den PC in. Da wir verschiedene Inhalte beachten müssen, *
; * werden wir auch  diesen  Daten eine Kennung verpassen.   Der Kopf wird   *
; *  mit „VALUE“ gesendet.   Das zu sendende Byte  wird in einem Register     *
; * Send_Byte der Routine Ser_Out übergeben                                                      *
; *  ****************************************************************
Send_Value :
	LDI	Send_Byte, ‚V’		; Telegramm-Kopf
	rcall   SerOut			
	LDI	Send_Byte, ‚A’
	rcall   SerOut			
	LDI	Send_Byte, ‚L’
	rcall   SerOut			
	LDI	Send_Byte, ‚U’
	rcall   SerOut			
	LDI	Send_Byte, ‚E’
	rcall   SerOut			
	LDI	ZH,High(Erste_Variable)
	LDI	ZL,Low(Erste_Variable)	; Adresszeiger auf 1. Variable
	LDI	Cnt_Reg, 30			; Anzahl zu sendender Bytes
Loop_Send:
	LDD	Send_Byte, Z+		; Lade inhalt der Adresse von Z und erhöhe Z
	RCall   SerOut			; Senderoutine aufrufen
	DEC	Cnt_Reg			; Bytezähler runterzählen
	BRNZ	Loop_Send			; Wenn nicht 0 dann weiter senden
RET
Für die Loop-Schleife werden wir später eine eigene Routine schreiben. Hier dient es der Übersichtlichkeit.

Fehlt nur noch der eigentliche Sendeanstoss
Code:
; ******************************************
; * Sendeanstoss der UART Schnittstelle RS 232   *
; * Übergabewert in Register Send_Byte                 *
;*******************************************

SerOut:	
	SBIS	UCSRA,UDRE			; Warten Freigabe UDR 
	RJMP	SerOut
    	OUT	UDR, Send_Byte
RET
 
Eingänge.

7. Eingänge.
Manchmal liegen die Eingänge nicht so schön in einem Port, wie wir uns das wünschen würden. Gründe mag es viele geben, von „bereits benutzt“ bis „ passt besser ins Layout“. Daher müssen wir auch lernen, verschiedene Bits zusammenzuführen. Dafür stehen uns die logischen Verknüpfungen AND, OR sowie Schiebeoperationen SHL oder ROR –Anweisungen zur Verfügung.
Code:
Read_IO: In Temp, PortC ; Port C in Register Temp schreiben
;Nun blenden wir unerwünschte Bits aus
ANDI Temp, 0b00001111 ; Nur Bit 0-3 sind für uns interessant.
; ab hier erweitern wir mit einem weiteren Reg.
In Merker, PortD ; Port C in Register Temp schreiben
;Nun blenden wir auch hier unerwünschte Bits aus
ANDI Merker, 0b00111100 ; angenommen, die Bits 2- 5 sind relevant.
ROL Merker ; schiebe nach links (Rotate left)
ROL Merker ; schiebe nach links (Merker =0b11110000)
OR Temp, Merker ; Oder-Verknüpfung, Ergebnis in Temp
STS Neu_Input_A,Temp ; neuen Wert in Variable ablegen
RET
[/CODE]
Jede Wette, das ich die Signalauswertung nicht anpassen muß. Es liegt einfach darin, das die Eingänge in einer Variablen zusammengefasst sind und mir einen Zyklus lang zur Verfügung stehen. Daraus bilden wir die Ereignisse, um angepasste Unterprogramme anzustoßen.

7.1 Eingänge verdoppeln
Manchmal benötigt man mehr IO-Pins. Auch dafür gibt es eine Lösung: die Matrix. Dazu werden die Ausgänge A1 und A2 abwechseln auf 0 geschaltet, danach jeweils E1 bis E4 eingelesen.
Code:
Read_IO:	In     Merker, Port D
                ORI	Merker, 0b00000011       ; Bit 0 und 1 setzen
                ANDI  Merker, 0b11111110       ; Bit 0 au 0 setzen
		Out	PortD, Merker                ; und ausgeben
		In 	Temp, PortC	          ; nun Port einlesen
		ANDI	Temp, 0b00001111	  ; nur die geschalteten Bits übernehmen			
                SWAP	Temp	                          ; Nibble tauschen. 
		In     Merker, Port D
                ORI	Merker, 0b00000011       ; Bit 0 und 1 setzen
                ANDI  Merker, 0b11111101       ; Bit 1 au 0 setzen
		Out	PortD, Merker                ; und ausgeben
		Out	PortD, Merker
		In 	Merker, PortC	
		ANDI	Merker, 0b00001111        ; nur die geschalteten Bits übernehmen	
		Or	Temp, Merker                 ; Register zusammenführen
		STS	Neu_Input,Temp             ; und in Variable schreiben
RET
Die Bits 0 und 1 in Port D schalten die Tasterebenen um. Im Anhang dazu eine Skizze. Die Dioden verhindern einen Kurzschluss bei Betätigen einer Taste in der 2. Ebene. Sie lassen nur 0- Signale durch.
 

Anhänge

  • Doppel_IO.jpg
    Doppel_IO.jpg
    12,2 KB · Aufrufe: 15
Eingänge vervielfältigen

7.2 Eingänge vervielfältigen

Zu diesem Zweck zeige ich noch einmal die Skizze von Abschnitt 3.6.

Ich kann die Ports so zusammenfassen, das ich in jeder Ebene 8 Bits liegen habe. Wie diese zusammengefasst werden, ist in der Read_IO beschrieben. Nun muß man sich die Frage stellen, muß ich für jede Ebene die Programme zur Flankenauswertung erweitern, oder geht das auch etwas eleganter.
Vielleicht hilft hier die Adressierung mit den Adressregistern X, Y und Z. Zuerst lösen wir Read_IO auf und setzen Parameter. Da wäre ein Adressregister für Neu_Input_x und ein Register, um die entsprechende Ebene Anzusteuern. Nehmen wir den Tabellenzeiger, er wird im Moment ja nicht gebraucht. Wir müssen nun die Bits 2 bis 6 von Port C einzeln auf 0 ziehen, damit nur diese eine Reihe der Taster eine 0 auf die Eingangspins schalten kann. Die anderen 3 Ausgänge von Port C müssen eine 1 erhalten. Die Dioden verhindern einen Kurzschluß und lassen nur 0 -Signale durch. Die Bits 0 und 1 sowie 6 und 7 dürfen dabei nicht verändert werden
 

Anhänge

  • IOKaskade.jpg
    IOKaskade.jpg
    17,9 KB · Aufrufe: 15
Die Auswahl der Eingangskanäle

7.2.1 Die Auswahl der Eingangskanäle
Code:
;****************************************************
;*                                            Kanalselektion                               *
;****************************************************                                              
Read_IO:	LDI	ZH,High(Neu_Input_A)
		LDI	ZL,Low(Neu_Input_A)	
                LDI	Tab_Cnt, 0b11111011	; 1. Kanal beschalten
                RCall	Read_IO_Kanal
                LDI	ZH,High(Neu_Input_B)
		LDI	ZL,Low(Neu_Input_B)	
                LDI	Tab_Cnt, 0b11110111	; 2. Kanal beschalten
                RCall	Read_IO_Kanal
                LDI	ZH,High(Neu_Input_C)
		LDI	ZL,Low(Neu_Input_C)	
                LDI	Tab_Cnt, 0b11101111	; 3. Kanal beschalten
                RCall	Read_IO_Kanal
                LDI	ZH,High(Neu_Input_D)
		LDI	ZL,Low(Neu_Input_D)	
                LDI	Tab_Cnt, 0b11011111	; 4. Kanal beschalten
                RCall	Read_IO_Kanal
RET
7.2.2 Einlesen der Eingangskanäle
Nun das eigentliche Lesen der Eingänge und die Ablage in die Variablen Neu_Input_A bis Neu_Input_D
Code:
;****************************************************
;*           Kanal Eingänge einlesen und in Variable ablegen      *
;****************************************************                                              
Read_IO_Kanal:	
		In 	S_Merker, PortC		; zuerst Port C lesen 
		ORI	S_Merker, 0b00111100	; alle Ausgabebits auf 1 setzen
		AND	S_Merker, Tab_Cnt	; nur Kanal-Bit auf 0 ziehen
		OUT	PortC, S_Merker		; Taster des selektierten Kanals aktiv
                In 	S_Merker, PortC		; Port C in Register S_Merker schreiben
				;Nun blenden wir unerwünschte Bits aus
		ANDI	S_Merker, 0b00000011	; Nur Bit 0-1 sind für uns interessant.
		In 	T_Merker, PortD		; Port D in Register T_Merker schreiben
				;Nun blenden wir unerwünschte Bits aus
		ANDI	S_Merker, 0b11111100	; Nur Bit 0-1 sind für uns interessant.
		OR 	S_Merker, T_Merker	; führen die Eingänge zusammen
		STD	Z, S_Merker		; und legen neuen Wert in Variable ab
RET
Mit diesen beiden geschachtelten Unterprogrammen kann ich nun 32 Eingänge einlesen und auch verarbeiten.
 
Auswerten von Änderungen der Eingänge

7.2.3 Auswerten von Änderungen der Eingänge
Nun gilt zu prüfen, ob auch die Auswertung in einer ähnlichen, parametrierten Form durchführbar ist. Auch hier müssen wir zuerst einen Verteiler vorsetzen, der die einzelnen Unterprogramme mit notwendigen Daten versorgt. Dazu behalten wir das unterprogram Chk_IO_Cange bei und machen eine Kopie , die das Label Chk_IO_Change_Kanal erhält. Nun verändern wir Chk_IO_Cangeso, das nur die Adressen der Variablen in Adressregister eingetragen werden. Dabei müssen wir beachten, das auch die Adressen der verwendeten Variablen in der untergeordneten Auswertung auf Adressregister eingetragen werden. Da Adressregister nicht unbegrenzt verfügbar sind und die 3 vorgesehenen schnell erschöpft sind, müssen wir uns etwas einfallen lassen. So liegen die Variablen Neu_Input_A und Old_Input_A direkt hintereinander. Neu_Input_x wird nur gelesen, also kann ich diesen Wert in ein freies normales Register übertragen und beim Lesen Z gleich erhöhen. Somit zeigt Z auf Old_Input_x und da ich diesen Wert zurückschreiben muß, erhöhe ich Z beim Lesen nicht mehr
Kommen wir zu IO_Change_Bit_x, welches sowohl gelesen als auch beschrieben wird. Daher vergeben wir den Adresszeiger X. Wenn wir nun die anderen zwei Variablen einer Eingabegruppe betrachten, das sind Chg_To_Low_x und Chg_To_High_x. Diese Variablen haben einen festen Adressenoffset zu Neu_Input_x und old_Input_x. Auch dies können wir uns in den weiteren Routinen zunutze machen.
Code:
; ******************************************************************
; *  Kanalweise prüfen der Eingänge inclusive der Flankenauswertung           *
; *  ****************************************************************
Chk_IO_Change:
                LDI	ZH,High(Neu_Input_A)		; Variablenadressen  Kanal A
		LDI	ZL,Low(Neu_Input_A)	
                LDI	XH,High(IO_Change_Bit_A)
		LDI	XL,Low(IO_Change_Bit_A)
                RCall	Chk_IO_Chg_Kanal		; Auswertung
                LDI	ZH,High(Neu_Input_B)		; Variablenadressen  Kanal B
		LDI	ZL,Low(Neu_Input_B)	
                LDI	XH,High(IO_Change_Bit_B)
		LDI	XL,Low(IO_Change_Bit_B)
                RCall	Chk_IO_Chg_Kanal		; Auswertung
                LDI	ZH,High(Neu_Input_C)		; Variablenadressen  Kanal C
		LDI	ZL,Low(Neu_Input_C)	
                LDI	XH,High(IO_Change_Bit_C)
		LDI	XL,Low(IO_Change_Bit_C)
                RCall	Chk_IO_Chg_Kanal		; Auswertung
                LDI	ZH,High(Neu_Input_D)		; Variablenadressen  Kanal D
		LDI	ZL,Low(Neu_Input_D)	
                LDI	XH,High(IO_Change_Bit_D)
		LDI	XL,Low(IO_Change_Bit_D)
                RCall	Chk_IO_Chg_Kanal		; Auswertung

RET
 
Änderungen der Eingänge eintragen

7.2.4 Änderungen der Eingänge eintragen
Code:
; ********************************************************************
; *  Es wird geprüft, ob eine Änderung eines  Einganges aufgetreten ist und        *
; *  das Ergebnis  in Die Variable IO_Change_Bit eingetragen.  Im Programm   *
; *  wird dieses Bit nach der  Bearbeitung gelöscht                                                   *
; *  ******************************************************************
Chk_IO_Chg_Kanal:
	LDD 	S_Merker, Z+		; Reg. S_Merker mit neuem Wert laden
	LDS	T_Merker, Z		; das letzte gelesene Byte holen
	EOR	T_Merker, S_Merker	; S_Merker ist nicht verändert, 
	BREQ End_Chk_Chg_Kanal	; Reg. T_Merker ist 0 , keine Änderung
	STD	X, T_Merker		; Bits ablegen, die sich geändert haben
	RCall	Chk _To_Low		; Aufruf der Prüfung Wechsel von 1 nach 0
	RCall	Chk _to_High		; Aufruf der Prüfung Wechsel von 0 nach 1
        STD	Z, S_Merker		; Unterprogramme dürfen Register nicht
; verändern
End_Chk_Chg_Kanal: 
RET
Wie wir sehen können, sind die Programme nur unwesentlich größer geworden, obwohl sich die Anzahl der Eingänge vervierfacht hat. Dabei könnte man mit geeigneter Hardware die 4 Bits für die Kanalselektion ohne Probleme auf 16 Kanäle aufstocken. !6 * 8 Eingänge, das wären 146 Signale, ohne das ich nun noch groß an den Programmen drehen muß. Lediglich die Variablen müssen eingerichtet werden und die 4 Ausgabebits müssen den eine Hex-Zahl bilden. Dazu kann man eine Variable hochzählen, von 0 bis 15 und dann den Wert in einem Register an die richtige Stelle schieben (Bit 2- 4 von Port C.  Der Rest sollte ohne Änderung funktionieren. Aber es fehlt ja noch die Flankenauswertung. Das schauen wir uns jetzt mal an.
 
Multikanal-Flankenauswertung

7.2.5 Multikanal-Flankenauswertung
Die Flankenauswertung geschieht in den Unterprogrammen Chk_To_Low und Chk_To_High . Diese passen wir jetzt entsprechend an. Wir wissen, unser neuer Wert ist noch in S_Merker enthalten, auf Z kann ich nicht mehr zugreifen, da dieser Zeiger bereits auf Old_Input_x zeeigt. In T_Merker befinden sich die veränderten Bits des gerade bearbeiteten Kanals. Das Register S_Merker und auch das Register T_Merker wird mit dem momentanen Inhalt noch gebraucht, daher werden diese Register über den Stack gesichert.
Code:
; *******************************************************************
; *  Prüfung Änderung von 1 nach 0 , Ergebnis steht in   Change_To_ Low_x   *
; *  *****************************************************************
Chk_To_Low:
	Push	S_Merker			; diese Registerinhalte werden noch gebraucht
	Push	T_Merker
	LDD	S_Merker, Z		; Z zeigt bereits auf diese Variable
 	And	T_Merker, S_Merker	; in T_Merker sind die veränderten Bits
        Push 	ZH				; Z wird aber noch gebraucht
        Push 	ZL				
	LDI	Temp, 11			; 11 Adressen Differenz zu Change_To_Low_x
	ADD	ZL, Temp			; Z berechnen 
	ADC	ZH, Zero			; und Addition mit Übertrag
	LDD	S_Merker, Z		; nur Änderungen eintragen, andere Bits
	Or	T_Merker, S_Merker	; müssen ihren Wert erhalten
	STD	Z, T_Merker		; und zurückschreiben
	POP	ZL				; Registerinhalte wieder herstellen
        POP	ZH
	POP	T_Merker
	POP	S_Merker
RET

und nun die andere Flanke

Code:
; 
; 
; *******************************************************************
; *  Prüfung Änderung von 0 nach 1 , Ergebnis steht in   Change_To_ High_x   *
; *  *****************************************************************
Chk_To High:
	Push	S_Merker			; diese Registerinhalte werden noch gebraucht
	Push	T_Merker
	And	T_Merker, S_Merker	; Eine neue 1 und die Änderung ergeben eine 1
	Push 	ZH				; Z wird aber noch gebraucht 
        Push 	ZL				
        LDI	Temp, 12			; 12 Adressen Differenz zu Change_To_High_x
	LDD	S_Merker, Z		; nur Änderungen eintragen, andere Bits
	Or	T_Merker, S_Merker	; müssen ihren Wert erhalten
	STS	Z, T_Merker
        POP	ZL				; Registerinhalte wieder herstellen
        POP	ZH
	POP	T_Merker
	POP	S_Merker
RET
 
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)