Keine Angst vor Assembler

Status
Für weitere Antworten geschlossen.
Interrupt für Eingänge

7.3 Interrupt für Eingänge

Der Atmega 8 besitzt 2 Interruptfähige Eingänge. Im normalfall benötigt man für die Eingänge keinen Interrupt, da die Zykluszeit im ms-Bereich liegt. Trotzdem gibt es Signale, die man nicht pollen kann. Pollen bedeutet, das einmal pro Zyklus die Eingänge gelesen werden. Angenommen, es gibt sehr kurze Signale, die man erfassen möchte, z.B. eine Lichtschranke, die ein durchfliegendes Objekt erkennen soll. Geschwindigkeit 120 KM/h und eine Länge von 3 cm
Da muß man schon ein wenig rechnen, um die Signalzeit zu erhalten.
120000m / 3600 sind 33,3 m/s. dies bedeutet, ein 3 cm langer Gegenstand ist in der Lichtschranke nur 0,9 ms.
Dieses Signal wird vermutlich nicht zuverlässig erfasst, es sei denn, die Zykluszeit ist immer unter 0,9 ms. Davon können wir aber nicht ausgehen, da Daten versendet, Tabellenwerte bearbeitet werden und div. Interrupts ( Timer/ USART) ebenfalls die Zykluszeit beeinflusst.
Sicher erfassen kann ich diesen Eingang also nur, wenn er einen Interrupt auslöst. Gibt es mehrere Signale, deren Signalzeit ebenfalls so gering ist, sind die 2 Interrupt -Eingänge schnell belegt. Daher lege ich über eine externe Verknüpfung die Eingänge auf den interrupt und zusätzlich auf einen normalen Eingang, den ich in der ISR erfasse.
unten die zugehörige Skizze.
 

Anhänge

  • Eing_Interrupt.jpg
    Eing_Interrupt.jpg
    14,1 KB · Aufrufe: 16
Arbeiten mit Tabellen

8. Arbeiten mit Tabellen

Stellen wir uns einmal vor, wir haben 4 verschiedene Wertetabellen und möchten diese jeweils auf Tastendruck versenden.
Eine Möglichkeit ist , die Ereignisbits der Tasten auszuwerten und jeder Taste eine eigene Routine zuzuordnen. Das Versenden kann dann über ein gemeinsames Unterprogramm erfolgen. Dazu wird das Adressregister Z benutzt. Die Tabellen sind im Speicher wie folgt abgelegt:

Werte_Tab_1: .Byte 10 ; Tabelle mit 10 Werten jeweils 1 Byte
Werte_Tab_2: .Byte 10 ; Tabelle mit 10 Werten jeweils 1 Byte
Werte_Tab_3: .Byte 10 ; Tabelle mit 10 Werten jeweils 1 Byte
Werte_Tab_4: .Byte 10 ; Tabelle mit 10 Werten jeweils 1 Byte

In der Auswertung der Eingänge ordnen wir Bit0 bis Bit 3 den Tabellen zu und fragen die Flankenbits ab, z.B. von der aufsteigenden Flanke. Der Grund, dadurch wird diese Routine auch nur 1 mal bei einem Tastendruck aufgerufen und nicht zigmal, wenn ich nur den Status des Einganges auswerte. Natürlich muß nach Bearbeitung das Flankenbit gelöscht werden. .
In einer bereits erklärten Auswertung haben wir die Flankenmerker in die Variable Change_To_High eingetragen.
Nun lösen wir die Übertragung an den PC über ein weiteres Unterprogramm aus. Da wir alle vier Tabellen abfragen, kann am Ende der Prüfung dieser Bereich der Flankenbits gelöscht werden.
Auf den folgenden Seiten werde ich auf diese Art der Tabellenbearbeitung näher eingehen.

Es ist vielleicht aufgefallen, das die angegebenen Routinen eine Seite nicht überschreiten. Nun, das dient nicht dazu, hier eine Seitennummerierung zu bekommen, sondern der Aufbau von Programmteilen sollte übersichtlich bleiben. Außerdem sind fertige Unterprogramme nach Ausdruck schnell aufgeschlagen und kontrolliert.
Für meine neuen Projekte kann ich auch gleich die ein oder andere Routine nutzen.C&P ist nicht verwerflich, wenn es eigene selbsterstellte Routinen sind. Daher achte ich immer darauf, zusammengehörende Codeabschnitte in einer Seite zu halten
8.1 Tabellen auswählen
Code:
; ********************************************************
; * Eingänge Wechsel von 0 mach 1 abfragen und eine Übertragung    *
; * der Daten in der entsprechenden Tabelle auslösen.                           *
; * Die Signalflanke ist in Register Temp abgelegt                                *
; ********************************************************
Chk_Signal_Flag:
	LDI 	Cnt_Reg, 10			; Anzahl der zu übertragenen Bytes
	LDS	Temp, Change_To_High
	LDI	Merker, 0b00000001
	And	Merker, Temp	
	BREQ	Chk_Sig_1
	LDI	ZH,High(Werte_Tab_1)
        LDI	ZL,Low(Werte_Tab_1)
        RCall	Send_Tab
Chk_Sig_1:
	LDI	Merker, 0b00000010
	And	Merker, Temp	
	BREQ	Chk_Sig_2
	LDI	ZH,High(Werte_Tab_2)
        LDI	ZL,Low(Werte_Tab_2)
        RCall	Send_Tab
Chk_Sig_3:
	LDI	Merker, 0b00000100
	And	Merker, Temp	
	BREQ	Chk_Sig_4
	LDI	ZH,High(Werte_Tab_3)
        LDI	ZL,Low(Werte_Tab_3)
        RCall	Send_Tab
Chk_Sig_4:
	LDI	Merker, 0b00001000
	And	Merker, Temp	
	BREQ	Chk_Signal_Flag_End
	LDI	ZH,High(Werte_Tab_4)
        LDI	ZL,Low(Werte_Tab_4)
        RCall	Send_Tab
Chk_Signal_Flag_End:	
	LDI	Merker, 0b11110000		; nur die unteren 4 Bits löschen
	AND 	Temp, Merker
	STS	Change_To_High, Temp	; und zurückschreiben
RET

8.2 Tabelleninhalte versenden
Aus unserer Auswertung der 4 Bits habe ich nun in Abhängigkeit ein Unterprogramm aufgerufen. Da alle Tabellen gleich sind und die Daten in Reihenfolge liegen, reicht es, wenn die Anzahl der Tabellenfelder und damit zu versendenden Daten fest vorgegeben werden.
Code:
; ***********************************************
; * Versenden von Bytewerten aus seiner Tabelle           *
; * Die Adresse des ersten Wertes liefert Register Z       *
; * Bei jedem Lesevorgang wird Z um 1 erhöht und       *
; * das Register „Merker„ erniedrigt. Bei Merker = 0    *
; * ist die Übertragung beendet                                         *
; ***********************************************

Send_Tab:
	Push		Cnt_Reg		; Wert merken
	Push		ZL			; alte Adresse merken
	Push		ZH

Send_Loop:
	LD		Send_Byte, Z+
	RCALL	SerOut
	DEC		Merker
	BRNE		Send_Loop
	POP 		ZH
	POP 		ZL			; und Adresse zurückholen
	Pop		Cnt_Reg		; und zurückschreiben

RET
Sieht richtig klein aus und das ist auch unser Ziel. Kleine übersichtliche funktionelle Unterprogramme. Allerdings macht es Sinn, das Register Cnt_Reg vor der Programmschleife zu sichern, wird es doch im nächsten Aufruf evtl. wieder benötigt.
Das Versenden der Variablen durch eine Anforderung des PC’s mit dem Befehl „v“ haben wir bereits besprochen. Dank dieser Technik, Werte an Unterprogramme mitzugeben, können wir nun statt der Loop-Schleife einfach mit dem RCall Send_Tab dieses Unterprogramm aufrufen.
Nun möchte ich euch noch zeigen, wie die Routine Chk_Signal_Flag um einiges kleiner werden kann. Auch wenn ich bis zu 8 Tabellenfelder habe.
Es gibt in dieser Routine ein paar Zeilen, die sich sehr ähnlich sind. Wenn ich z. B. im Sendeaufruf das Z-Register wieder zugückschreibe, erhalte ich den alten Z-Wert zurück. Deshalb hab ich auch gleich das Z Register über den Stack gesichert. Wenn nun die Tabellen hintereinander vereinbart sind, weiß ich den Adress-Abstand zwischen den Tabellen. Und nun wenden wir einen kleinen Trick an:
 
Parametrierbare Unterprogramme

8.3 Parametrierbare Unterprogramme
Code:
; ********************************************************
; * Eingänge Wechsel von 0 mach 1 abfragen und eine Übertragung    *
; * der Daten in der entsprechenden Tabelle auslösen.                           *
; * Die Signalflanke ist in Register Temp abgelegt                                *
; ********************************************************
Chk_Signal_Flag:
	LDI 	Cnt_Reg, 10			; Anzahl der zu übertragenen Bytes
	LDS	Temp, Change_To_High
	LDI	Merker, 0b00000001
        LDI	ZH,High(Werte_Tab_1)
        LDI	ZL,Low(Werte_Tab_1)
	LDI	Zeiger, 4
Loop_Chk_sig:
	Push	Merker
	And	Merker, Temp	
	POP	Merker
	BREQ	Next_Step
        RCall	Send_Tab
Next_Step:
	DEC	Zeiger
	BREQ	 Chk_Signal_Flag_End:
	SHL	Merker			; Bit in Merker nach links schieben
	ADD	ZL, Cnt_Reg			; Adresse auf nächste Tabelle setzen 
	ADC	ZH, 0			
        RJMP Loop_Chk_sig
Chk_Signal_Flag_End:	
	LDI	Merker, 0b11110000		; nur die unteren 4 Bits löschen
	AND 	Temp, Merker
	STS	Change_To_High, Temp	; und zurückschreiben
RET

Obwohl diese Routine immer noch dieselbe Arbeit leistet, ist sie wesentlich anpassungsfähiger. Vergrößert sich die Tabelle auf 15, 18 oder gar 30 Felder, so braucht nur Cnt_Reg angepasst werden. Kommen noch Tabellen hinzu, reicht die Anpassung von dem Register „Zeiger“.
Merken wir uns also: Möchten wir Programme schreiben, die leicht anpassungsfähig sind, müssen wir möglichst viele Unterprogramme mit überschaubarer Anzahl von Befehlen haben. Hinzu kommt, das immer wieder die Adressierung geprüft werden sollte. Zeiger, Zähler, Adressregister- dies sind keine Zauberworte. Sie helfen uns, Programmschnipsel zu schreiben, deren Funktionalität manchmal erst bei fertigen Programmen zur vollen Wirkung kommen.
 
Tabellen kopieren

8.4 Tabellen kopieren

Hier definieren wir einfach einmal Quelle und Ziel. Werte von einer Quelle zu einem Ziel zu übertragen ist keine Kunst. Diese Übung dient nur dem Verständnis:
Code:
Quelltabelle: 	.Byte10
Zieltabelle:		.Byte10
Tab_Copy:
	LDI	ZH,High(Quelltabelle)
	LDI	ZL,Low(Quelltabelle)
	LDI	XH,High(Zieltabelle)
	LDI	XL,Low(Zieltabelle)
	LDS	Cnt_Reg, 10
Loop_Copy:
	LD	Work_Byte, Z+
	ST	X+, Work_Byte
	DEC	Cnt_Reg
	BRNZ	Loop_Copy
Ret
Um die Daten in einen EEProm zu schreiben, braucht nur statt ST X+, Workbyte ein
RCall Write_EEProm- Aufruf erfolgen. Die zugehörige Routine lautet wie folgt:
Code:
Write_EEProm:
    	sbic    EECR, EEWE		; letzte Schreibvorgang beendet? 
	rjmp    EEPROM_write		; warten
	out     EEARH, XH		        ; Adresse schreiben
	out     EEARL, XL        
	out     EEDR,Work_Byte		;Daten eintragen    
	in      Temp, SReg	        ; Statusregister sichern
	cli				        ; Interrupt Sperren, nicht unterbrechen    				
	sbi     EECR,EEMWE	        ; Schreiben anstoßen
	sbi     EECR,EEWE		        ; und eintragen    
	out     sreg, Temp	        ; Statusregister wieder herstellen
RET

oder lesen statt LD Work_Byte, Z+ nur RCall Read_EEProm aufrufen.

Code:
Read_EEProm:
   	sbic    EECR,EEWE              	; letzte Schreibvorgang beendet? 
	rjmp    EEPROM_read            	; warten 
	out     EEARH, ZH                	; Adresse laden
	out     EEARL, ZL    
	sbi     EECR, EERE            	; lesen aktivieren
	in      Work_Reg, EEDR          ; Daten eintragen
 ret
Bei beiden Zugriffen auf den EEProm muß das jeweilige Adressregister um 1 erhöht werden.
Hier gibt es weitere Informationen.

http://www.mikrocontroller.net/articles/AVR-Tutorial:_Speicher
 
Programmlaufzeiten

9. Programmlaufzeiten
Kommen wir nun zu den Laufzeiten. Es ist immer wieder zu beobachten, das Warteschleifen in ein Programm eingebaut werden. Dies bedeutet, in dieser Zeit wird nichts mehr bearbeitet. Damit erhöht sich die Zykluszeit und der Controller verliert seine Vorteile, die er durch die schnelle Befehlsverarbeitung von Hause aus mitbekommen hat. Gehen wir einmal davon aus, das ein Befehl im Durchschnitt 2 Takte benötigt, so haben wir bei einer Taktfrequenz von 16 MHz einen Befehl in 0,000000125 Sek. abgearbeitet. Das sind 125 nSek. Nun zählen wir einfach mal alle Befehle vom Hauptprogramm, addieren die Befehle der Aufgerufenen Unterprogramme dazu und erhalten die Anzahl aller Befehle im Programmzyklus. Schleifen müssen entsprechend oft gezählt werden. Das Ergebnis mit 0,000000125 Sek multipliziert ergibt die theoretische Zykluszeit. Vermutlich werden wir bei ca. 5-12 ms liegen.
Auch Interruptroutinen müssen auf diese Weise berechnet werden. Dabei wird sehr schnell klar, wo die Grenzen liegen. Ein Drehimpulsgeber mit 1024 Imp/ U. und einer Drehzahl von 2400 U/min liegt bei ca. 40 KHz (Periode bei 1KHz = 1 mS, bei 40 KHz =25 µS)
Da dürfen selbst die Interruptroutinen nur wenige Befehle ausführen, damit nicht der nächste Interrupt in die laufende ISR fällt.
Die programmierten Wartezeiten in den Delay- Anweisungen machen allerdings diese Betrachtung zunichte.
Die bessere Alternative ist der Timer-Interrupt. Wie er entsteht und wie er programmiert wird, ist bereits besprochen. Wenn der Timer alle Millisekunden eine ISR aufruft, kann ich ca. 400 Befehle programmieren und belaste meinen Zyklus nur mit 100 µS je errechneter mS. Also, angenommen der Zyklus dauert normal ca. 12 mS, dann wird in dieser Zeit 12 mal der Interrupt aufgerufen, was dann zusätzlich noch einmal ca. 4 ms bringt. So weiß ich aber, mein Programm ist wirklich 1 mal in ca. 15 ms durchlaufen und hat alle Bearbeitungsschritte erledigt. Die angegebenen Werte sind allerdings nur theoretisch. Ein Programm verzweigt sehr oft, Unterprogramme werden nicht alle aufgerufen und viele Programmteile übersprungen. Das verkürzt natürlich die Zykluszeit. Wenn man sich das Berechnen einfach machen möchte, könnte man es prozentual bewerten. Andererseits gibt es weitere Timer, die noch nicht genutzt werden. Allerdings können sie nur 1 Byte aufnehmen, das heißt einen Wert von 255, dann fangen sie wieder mit 0 an.
Hier bietete sich an den Überlauf-Interrupt zu nutzen.Berechnen wir einmal den zu erwartenden Wert, um abschätzen zu können, wie weit theoretisch gezählt werden muß.
Wie lang ein Takt dauert, ist aus der Formel 1/ FCpu berechenbar. Also bei 16 MHz dauert 1 Takt 62,5 nS. Das wäre ein Zählerwert von 256000 bei einer Zykluszeit von 16 ms. Das ist für einen 8 Bit Counter ein zu großer Wert. Selbst ein Teiler von 255 würde immer noch 1000 Schritte zählen.
Daher lassen wir ihn so und lassen ihn überlaufen. Das würde bedeuten, er läuft während eines Programmdurchlaufes ca. 1000 mal über und löst dabei das Timer Overflow-Flag aus. Nun werden wir diese Interrupts in einer ISR einfach zählen.
 
Mit 2.Timer Laufzeit berechnen

9.1 Mit 2.Timer Laufzeit berechnen
Wenn wir aber auch hier einen Interrupt nutzen und den Wert in der ISR noch mal um 100 teilen, dann erhalten wir ein zweites Byte mit ca. 10 Imp. Diese Werte müssen für einen Durchlauf konstant sein, daher brauchen wir dafür 4 Bytes in der Variablenablage.
Code:
Zykl_Zeit_1: 	.Byte1		; für Zählerstand
Zykl_Zeit_2	        .Byte1
Zykl_ 1: 	        .Byte1		; für den aktuellen Zähler
Zykl_ 2	        .Byte1

Die Variablen Zykl_ 1 und Zykl_ 2 werden am Anfang des Programmes auf die Variablen Zykl_Zeit_1 und Zykl_Zeit_2 kopiert. Danach werden die Variablen Zykl_ 1 und Zykl_ 2 auf 0 gesetzt. Damit erhalte ich eine einigermaßen genaue Zykluszeit.

Code:
Loop:	LDS	Temp, Zykl_ 1
	STS	Zykl_ Zeit_1, Temp
	LDS	Temp, Zykl_ 2
	STS	Zykl_ Zeit_2, Temp
	CLR	Temp
	STS	Zykl_ Zeit_1, Temp
	STS	Zykl_ Zeit_2, Temp
	…..  hier nun unser Programm ….

Nun die zugehörige ISR für Timer 0

Code:
isrTimer0:				; Sprungmarke aus der Interrupt Vector Tabelle
	Push	Temp			; Register 1 sichern
	In	Temp, SREG		; Statusregister holen
	Push	Temp			; und auf Stack sichern
	LDS	Temp, Zykl_ 1
	Inc	Temp
	CPI	Temp, 100
	BRLO	Set_Zykl_1		; wenn < 100 dann ablegen und weiter
	Clr	Temp
	STS	Zykl_ 1, Temp
	LDS	Temp, Zykl _2
	Inc	Temp
	STS	Zykl_ 2, Temp
	RJmp	isrTimer0_End

Set_Zykl_1:
	STS	Zykl_Zeit_1, Temp
isrTimer0_End:
	POP	Temp			; und auf Stack sichern
	OUT	SREG,Temp		; Statusregister holen
        POP	Temp			; und auf Stack sichern
RETI
 
Hallo oldmax,
:adore: :adore: :adore:
Das ist absolut super, was du da zusammengestellt hast und der Gemeínde zur Verfügung stellst.
Bisher habe ich um Assembler noch einen großen Bogen gemacht nur mal hier und da ein paar Befehle. Deine Zusammenstellung hat mich nun überzeugt, doch mal genauer reinzusehen, ob ich nicht mehr damit machen kann.

HBA

PS: Wenn dieses Post hier ungelegen ist, weil du noch weiterschreiben willst oder so, können wir es gerne wieder rausnehmen lassen. Wollte ich aber einfach mal loswerden.
 
Hi
Nein, ich glaube, wenn ich das alles aufgefüllt habe, bin ich mit Assembler (erst mal) durch. Ich hoffe, das die Routinen alle laufen, denn aus Zeitmangel hab ich nur die Übersetzung mit Studio geprüft. Dieses Programm sollte lauffähig sein und die Kommunikation mit OpenEye ermöglichen. Dann wird's noch spannender, das könnt ihr mir glauben. Wenn erst mal auf die Flags geschaut werden kann, die Variablen beobachtet und der Laufzeitzähler richtig Tickt, habt ihr für die Eigenentwicklung ein gutes Werkzeug. Immerhin, ihr seid jetzt Tester, denn das ist ein Auszug aus einem Kurs für µC und PC. Also, die Diskussion ist eröffnet....
Gruß oldmax, der jetzt den Rest fertigstellen muß.
 
Hi oldmax,

ich glaube das muß ich mir mal alles in Ruhe mit nem Bierchen daneben am
Wochenende ansehen ;) Ist auf jeden Fall ne ganze Menge Stoff :D
Ich mach zwar schon ne ganze Zeit Assembler aber evtl kann man ja doch
noch das eine oder andere dazulernen oder optimieren :cool:

Ich packs mal mit in das Inhaltsverzeichnis ...

Gruß
Dino
 
Hallo Oldmax,
ich glaube das muß ich mir mal alles in Ruhe mit nem Bierchen daneben am Wochenende ansehen ;)
... da kann ich nur Dino zustimmen :D

Also dein Einblick in Assembler ist schon recht detailliert. Ich habe deinen Assemblerkurs auf Grund von Zeitmangel erst nur mal überflogen und werde mir diesen aber am Wochenende nocheinmal genauer durchlesen. Ich denke mal, was es dem Anfänger schwer macht, ist überhaupt einen Anfang zu finden. Ich hatte irgendwann mal die Struktur eines Assemblerprogramms, so wie ich sie bevorzuge, anhand eines Templates erläutert, mal sehen, ob ich das noch finde :rolleyes: Aber wie schon gesagt, du hast sehr detailliert geschrieben und da wird sich doch vielleicht mal der eine oder andere näher mit Assembler beschäftigen und auch wenn es nur darum geht, Assembler in eine Hochsprache wie zum Beispiel Bascom einzubauen ;)

Dankeschön für das ausführliche und interessante Thema! ... werde ich mich noch näher damit beschäftigen :).

Grüße,
Dirk
 
Hallo oldmax !

BOAH.... ein WAHNSNN ! :eek: :eek: :eek:

Was ist denn hier passiert?
Da brauche ich ja einige Wochenenden um das nicht nur zu lesen... sondern auch in meinen Schädel zu bekommen und zu verstehen! :stupido2:

Sag mal, oldmax, kann ich das alles auch als Buch zum Nachschlagen bekommen? ;)

Ist ja echt der Wahnsinn, was du hier alles beschrieben hast! :flowers:


Ehrfürchtige Grüße :adore:
Cassio
 
Hi
Zu Dirk und Dino03, also, das WE sollja schön werden... hmmm, ich glaub, ein Bierchen werd ich mir jetzt auch gönnen.:cheers:
Ein paar Worte, warum das hier steht. Ich bin kein Profi, zumindest nicht im Bereich der µC. Allerding "optimiere" :pleasantry: ich schon einige Jahrzehnte an großen Prozessanlagen. Da bekommt man schon ein wenig mit auf den Weg.
Was mir so im allgemeinen immer wieder auffällt, sind die Schwierigkeiten der Anfänger, zu verstehen, warum ein Controller dies oder jenes (nicht) tut. Programme schreiben bedeutet aber auch, eben dies herauszubekommen. Ich hatte das Glück, noch mit einem Z80 einzusteigen...:D Da war man gezwungen, selbst Hand anzulegen, wollte man nicht das ganze Taschengeld, was einem die teure Gattin zukommen ließ, für's Hobby verbraten. Immerhin hat der damals mit einer wahnsinnigen Speicheraufrüstung von 16 K 595 DM gekostet.
Über die Jahre wuchsen auch die PC's heran. Trotzdem gab es hier und da mal ein paar Projekte mit IC's und so. Schließlich hab ich meinem Spieltrieb ja auch meine Karriere zu verdanken, denn gelernt hab ich mal Elektroinstallateur.
Ja, die Kid's von heute werden gedrillt auf schnelle Erfolge und oft wird diese Einstellung noch von Profis unterstützt. Ich geh da einen etwas anderen Weg, der nicht unbedingt schwerer sein muß. Aus diesem Grund hab ich mal ein wenig gezeigt, das Assembler eben nicht schwierig sein muß. Ok, bei Gleitkommezahlen hört's bei mir auch auf. Da werd ich dann eine "Hochsprache" bemühen.
Da ich auf der anderen Seite auch PC's mit meinen Programmen quäle, war für mich ein interessanter Weg µC Hardware und PC Visualisierung entstanden. Na ja, nicht immer liege ich da im Zeitplan, wobei ich an meine Alarmanlage für Holzstapel denke, die weit draußen in der Pampa auf frierende Banditen geradezu verlockend wirken..... bei Auslösung Anruf von Prepaid- Handy. Programm ist fertig, aber die Handys wollen nicht so...
Aber eben das macht es so spannend. Ein Controller, ein Max, ein bischen drumrum und schon fertig..... die Hardware zumindest.
Ach ja, nächstes Jahr denk ich, muß die Runden- und Zeiterfassung für 'ne Slotcarbahn fertig sein, sonst ist mein Enkel traurig. (Die Datenbank ist noch nicht so, wie ich's gern hätte und meine Delphi3 Version wird langsam alt... Daher ist jetzt Visual Basic angesagt. Aber da muß ich mich noch etwas einarbeiten. (Delphi ist für's Hobby einfach zu teuer..)
Ok, so kommt eine Anwendung zur anderen. Und genau hier setze ich an, Wissen zu vermitteln. Es gibt keine Idee, die keine Energie fordert. Meine Message soll sein: habt keine Angsat vor großen Zielen. Es dauert, aber auf dem Weg dorthin müßt ihr viele Probleme lösen. Dazu gehört dann auch schon mal der Blick in die Grundlagen. Nur das macht euch über die Zeit fit. Nicht schnelle Erfolge sind dauerhaft, sondern auch bei langwierigen Aufgaben sich den Problemen stellen und Lösungen suchen. Das schafft solides Basiswissen.
Ich hoffe, mit meinem Beitrag etwas zurück gegeben zu haben, was ich an anderer Stelle auch erhalten habe.
Gruß oldmax
PS: wenn ich soweit bin, könnte ich ja mal über Programmierung PC was bringen....
 
Hi oldmax,

auch ich bin begeistert und muss, wie die Anderen, das alles mal ganz in Ruhe durcharbeiten.

Ich werde zwar auf keinen Fall zu Assembler wechseln, doch werden mir Deine Erklärungen bestimmt helfen, die Eine oder Andere ASM- Sub in Bascom einzubauen.


Grüsse,

Michael
 
Hi
Ich werde zwar auf keinen Fall zu Assembler wechseln,
.....
Schade.. da hab ich doch gedacht, ich könnt da missionarisch unterwegs sein :)
Nun, es ist gar nicht wichtig, welche Programmiersprache man nutzt, sondern wie man programmieren "versteht". Im Prinzip ist das Ergebnis wichtig und wenn da ein tolles Programm herauskommt, welches nicht mehr verändert werden muß, dann akzeptiere ich auch den wildesten Spaghetticode. Andererseits, und das sollten wir Anfängern vermittleln, ist strukturiertes Programmieren ohne Leistungsverlust in jeder Sprache möglich. Ich kann mich noch gut erinnern, als mir mal auf der Volkshochschule in einem Pascal-Kurs erzählt wurde, Basic ist für strukturiertes Programmieren nicht geeignet, das geht nur in Pascal, bzw. nur in Compilersprachen. Lang hat's nicht gedauert, bis ich gesehen habe, das meine Programme in Basic bereits ähnliche Strukturen aufwiesen.
Gut, das ist über 30 Jahre her und in der Zwischenzeit bin ich mit Delphi ganz gut unterwegs, werde aber aus Kostengründen VB - Anwendungen umsetzen. Übrigends, das mit der Rennbahn ist eine Delphianwendung, die vermutlich als nächstes auf VB kommt.
Doch wenn es interessiert, ich denke, ihr habt alle schon mal einen Blick in OpenEye geworfen,( wenn nicht, das kleine Assemblerprogramm ist bestens geeignet dazu) würde ich euch vorschlagen, auch mal eine Appllication in VB für eine Controller-Anwendung in Entwicklungsschritten vorzustellen.
Keine Angst, es könnte da keine Fragen an uns mehr geben. Ich denke, wer sich an die Thematik µC heranwagt, hat immer Fragen. Das hier das Frage-Antwortspiel nicht so hoch frequentiert ist, liegt ein wenig in der Bequemlichkeit. Oft hat man eine Antwort zu einem Beitrag geschrieben und es bleibt die Erfolgsmeldung aus. Ich persönlich find es dann halt schade, aber das ist nun mal so. Ich denke auch, einige verlieren die Lust, weil andere Interessen stärker sind. µC sind eben nicht zum Prahlen auf dem Schulhof mit dem Wissen aus irgendeinem Forum, sondern entweder für den Beruf oder für's wirkliche Hobby. Und gerade letztere haben schon ein wenig Basiswissen. (bei den anderen setz ich das einfach mal voraus, das sind auch nicht unbedingt hier Fragesteller...) Also, ich hab da keine Bedenken, das aus diesen Themen wie Assembler und Basic so viel Wissen entnommen wird, das das Forum einschläft. Eher das Gegenteil.
So, nun nutz ich aber noch ein bischen das schöne Wetter.
Gruß oldmax
 
Schade.. da hab ich doch gedacht, ich könnt da missionarisch unterwegs sein :)
nun, Du hast den zweiten Teil im Zitat weggelassen. Komplett umsteigen nicht, doch einige Routinen werde ich bestimmt irgendwann einmal innerhalb von Bascom benötigen.

Zwischen den Sektoren werden ca. 5 Sekunden Zeit sein, endlos lange für einen AVR.
Doch die gleichzeitige Kommunikation zwischen den Atmels und dem PC sowie der Display-Ansteuerungen könnte schon mal knifflig werden. Und dann kommen Dein Lehrgang (und Dinos Beispiele) zur Anwendung.:)


Grüsse,

Michael
 
Hi,

hmm, hab ich schon wieder 1:0 verloren?:rolleyes:


Grüsse und schöne Woche,

Michael
 
Hi
Nun kommt, wie im Beitrag 47 versprochen noch ein lauffähiger Code, allerdings ist er zu Groß, um in einen Beitrag hineinzupassen. Deshalb werde ich ihn auf 4 verteilen
Hier Teil 1:
Code:
; **************************************************************************************
; *  Dieses Programm dient der Übung mit Assembler und liefert die Basis  für          *
; *  eigene Ideen. So sind ein paar Funktionen eingebaut, die zur Bearbeitung          *
; *  von Eingängen hilfreich sind. Nicht alles ist geprüft und muß bei Anwendung       *
; *  selbst kontrolliert und angepasst werden. Die Kommunikation mit OpenEye ist       *
; *  getestet und kann zur Sicht auf die Variablen benutzt werden. Zur Zeit reagiert   * 
; *  dieses  Programmauf die Steuerzeichen vom PC "V" und "v". mit der Antwort der     *
; *  Variableninhalte bis Zykl_2. Tabellen können noch nicht in OpenEye gelesen        *
; *  werden. Anpassungen sind in dem Unterprogramm "Send_Value" vorzunehmen.           *
; **************************************************************************************




;IO -Register
.NoList
.include "m8def.inc" ; Definitionen für ATMEGA8
.List

; ********************************************************************
; *                    Zuerst die Deklarationen                      *
; ********************************************************************

;---------------------------------------------------------------------
 

.EQU 	F_CPU		= 8000000				; Systemtakt in Hz
.EQU 	BAUD		= 19200					; 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 

.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


.DEF Access		= R0 		; Speicherzugriffsregister
.DEF Zero		= R1 		; Zwischenspeicher aus anderen Merkern
.DEF Ablage		= R2 		; Zwischenspeicher aus anderen Merkern
.DEF Number		= R3 		; Anzahl der Wertebyte

.DEF Event_Reg		= R4 		; Ereignisse
.DEF Referenz		= R5		; Vergleiche
.DEF Prg_Flags		= R6		; Programmstatus
.DEF ms0		= R7		; Millisekunden * 10^0
.DEF ms1		= R8		; Millisekunden * 10^1
.DEF ms2		= R9		; Millisekunden * 10^2
.DEF Sekunde		= R10		; Sekunden
.DEF Minute		= R11		; Minuten
.DEF Stunde		= R12		; Stunden
.DEF Tag		= R13		; Tag
.DEF Woche		= R14		; Woche
.DEF Tab_Cnt		= R15		; Zähler
.DEF Byte_Cnt		= R16		; Zähler
.DEF Cnt_Reg		= R17		; Zähler
.DEF S_Merker		= R18		; Allzweckregister
.DEF T_Merker		= R19		; Allzweckregister
.DEF Bef_Ctrl		= R20		; Befehlskontrolle
.DEF Prg_Ctrl		= R21		; Zeiterfassung min
.DEF Work_Reg		= R22		; Zeiterfassung Std
.DEF Merker		= R23		; Rechenschritte
.DEF Temp		= R24		; Vergleiche
.DEF Send_Byte		= R25		; Sendewert
; Register 26- 31 sind bereits vergeben für die Adressregister X, Y und Z
 
; ************************************************************
; * Es folgen die Variablendeklarationen in Daten-Segment    *
; ************************************************************
.DSEG
Erste_Variable:		.Byte 1		; diverse Variablen aus den Beispielen
Signal_Chg_1:		.Byte 1
Neu_Input:		.Byte 1
Old_Input:		.Byte 1
Neu_Input_A:		.Byte 1
Old_Input_A:		.Byte 1
Neu_Input_B:		.Byte 1
Old_Input_B:		.Byte 1
Neu_Input_C:		.Byte 1
Old_Input_C:		.Byte 1
Neu_Input_D:		.Byte 1
Old_Input_D:		.Byte 1

IO_Change_Bit:		.Byte 1
IO_Change_Bit_A:	.Byte 1		;bei vervielfachung der Eingänge
IO_Change_Bit_B:	.Byte 1
IO_Change_Bit_C:	.Byte 1
IO_Change_Bit_D:	.Byte 1
Change_To_Low:		.Byte 1
Change_To_High:		.Byte 1
Change_To_Low_A:	.Byte 1
Change_To_High_A:	.Byte 1
Change_To_Low_B:	.Byte 1
Change_To_High_B:	.Byte 1
Change_To_Low_C:	.Byte 1
Change_To_High_C:	.Byte 1
Change_To_Low_D:	.Byte 1
Change_To_High_D:	.Byte 1
Ausgabebyte:		.Byte 1
Zaehlbyte:		.Byte 1
Bef_Control:		.Byte 1
Bef_Cnt:		.Byte 1
Cnt_Soll:		.Byte 1
Value_Dir:		.Byte 1

Timer_Flag:		.Byte 1		; Bit 0 = 1ms
					; Bit 1 = 10 ms
					; Bit 2 = 100
					; Bit 3 = Sekunde
					; Bit 4 = Minute
					; Bit 5 = Stunde
					; Bit 6 = Tag
					; Bit 7 = Woche

	
Write_Pos:		.Byte 1		; Zeiger für die Schreibposition im Ringspeicher
Read_Pos:		.Byte 1		; Zeiger für die Leseposition im Ringspeicher 
Zykl_Zeit_1: 		.Byte 1		; für Zählerstand
Zykl_Zeit_2:		.Byte 1
Zykl_1: 		.Byte 1		; für den aktuellen Zähler
Zykl_2:			.Byte 1

Kanal_0:		.Byte 20
Ring_Buf:		.Byte 50
Werte_Tab_1: 		.Byte 10	; Tabelle mit 10 Werten jeweils 1 Byte
Werte_Tab_2: 		.Byte 10	; Tabelle mit 10 Werten jeweils 1 Byte
Werte_Tab_3: 		.Byte 10	; Tabelle mit 10 Werten jeweils 1 Byte
Werte_Tab_4: 		.Byte 10	; Tabelle mit 10 Werten jeweils 1 Byte
Quelltabelle:		.Byte 45
Zieltabelle:		.Byte 45

; ************************************************************ 
; *  Es folgt das Codesegment  mit Programmsprung über die   *
; *  Interrupt Vektor Tabelle zum Startpunkt.                *
; ************************************************************
.CSEG
.org 0000
Reset:	RJmp Start			;Überleitung zum Programmeinsprung

; *******************************************************************
; *                          Interrupt Vektor Tabelle               *
; *******************************************************************


.org INT0addr	RETI			; Ext. Int.0 Vector Address   	ausgeblendet       

.org INT1addr 	RETI			; Ext. Int.1 Vector Address   	ausgeblendet

.org OC2addr	RJmp 	isrTimer2	; Output Compare2 Int. Vector Addr.       

.org OVF2addr 	RETI			; Overflow2 Int. Vector Addr. 	ausgeblendet

.org ICP1addr	RETI			; Input Capture1 Int. Vect. Addr.	ausgeblendet   

.org OC1AAddr	RJmp 	isrTimer1   	; Einsprungadresse ISR Timer1

.org OC1BAddr  	RETI			; Output Compare1B Int. Vector Addr.ausgeblendet  

.org OVF1addr

.org OVF0Addr	RJmp 	isrTimer0  	; Einsprungadresse ISR Timer0

.org SPIaddr	RETI			; SPI Interrupt Vector Address 		ausgeblendet      

.org URXCaddr 	RJmp	int_rxc		; USART Receive Complete Interrupt Vector Address       

.org UDREaddr 	RETI			; USART Data Register Empty Int.Vect.Addr. ausgeblendet   

.org UTXCaddr   RETI         

.org ADCCaddr	RETI			; ADC Interrupt Vector Address    		ausgeblendet

.org ERDYaddr	RETI			; EEPROM Int. Vector Address     		ausgeblendet 

.org ACIaddr	RETI			; Analog Comp. Int. Vect. Addr.     	ausgeblendet  

.org TWIaddr	RETI 			; Irq. vect. addr. for Two-Wire Interface	ausgeblendet   

.org SPMRaddr 	RETI			; SPM complete Int. Vect. Addr.       	ausgeblendet
                  
				

 
 
; ************************************************************
; *             Programmbegin Initialisierung                *
; ************************************************************
Start :					; ------------ Initialisierungsbereich -----------------
	LDI	R26,high(RAMEND)	; Stack Pointer setzen 
	OUT	SPH,R26			; "RAMEND" ist in m8def.inc (s.o.) festgelegt
	LDI	R27,low(RAMEND)		; 
	OUT	SPL,R27      
			
	RCall	InitTimer0		; ---- Initialisierungsbereich Timer, Usart etc. -------
	RCall	InitTimer1	
	RCall	InitTimer2	
	RCall	Init_Uart
	RCall	Init_Port
	RCall	InitRegister
	RCall	InitVariable
	RCall	Reset_Tabellen

	SEI				; Freigabe Interrupt
	;RCall	Toggle_PrtD5

Loop_Main:				; --------         Programmschleife          ---------
	

	RCall  	Read_IO			; Eingänge lesen
	RCall  	Chk_RS232_Empf		; Lese serielles Interface
	RCall  	Chk_IO_Change		; Wechsel von Eingängen feststellen
	RCall   Chk_Signal_Flag		; Prüfen und reagieren auf Ereignisflags
	RCall	Chk_Timer_Flag		; Zeitereignisse prüfen und reagieren
	RCall	Write_UART		; beschreibe serielles Interface
	RCall  	Write_IO		; beschreibe die Ausgaben
	RCall	LaufZeit		; setze Laufzeit

RJMP	Loop_Main
 
Fertig Code Teil 2

Hi
Dieser Teil besteht aus den Initialisierungen . Der UART ist auf 19200 Baud bei 8 MHz eingestellt,
Code:
; *************************************************************
; *                    Bereich Unterprogramme                 *
; *                   zuerst die Initialisierungen            *
; *************************************************************
;--------------------------------Timer 1 Parametrierung Overflow----------------------
InitTimer0:
	
	;ldi	temp, 0b00000001      
   	;out	TCCR0, temp

	 
 	;In 	S_Merker, TIMSK
	;ORI	S_Merker, 0b00000001
	;OUT 	TIMSK, 	S_Merker  
RET

;--------------------------------Timer 1 Parametrierung Compair ----------------------

InitTimer1:		
	LDI	S_Merker, high( 2000 - 1 )
	OUT	OCR1AH, S_Merker        
	LDI	S_Merker, low( 2000 - 1 )        
	OUT	OCR1AL, S_Merker    
	 ; CTC Modus einschalten                                   
	; Vorteiler auf 8        
	LDI	S_Merker, 0b00001010	;( 1 << WGM12 ) | ( 1 << CS11 )        
	OUT	TCCR1B, S_Merker         
	In 	S_Merker, TIMSK
	ORI	S_Merker, 0b00010000
	OUT	TIMSK, S_Merker
RET

;--------------------------------Timer 2 Parametrierung Compair---------------------

InitTimer2:

	ldi     temp, 100    
    	out     OCR2, temp

	ldi     temp, 0b00101001      
    	out     TCCR2, temp

	;LDI 	S_Merker, 	(1 << TOIE0)	; TOIE0: Interrupt bei Timer Overflow        
	;OUT 	TIMSK, 	S_Merker    
 	
	In 	S_Merker, TIMSK
	ORI	S_Merker, 0b10000000
	ANDI	S_Merker, 0b10010001
	OUT 	TIMSK, 	S_Merker  

RET
;------------------- Serielle Schnittstelle parametrieren  -------------------------------------------
INIT_UART:
	LDI	S_Merker, 	HIGH(UBRR_VAL)		; Baudrate einstellen
	OUT	UBRRH, 		S_Merker
	LDI	S_Merker, 	LOW(UBRR_VAL)
	OUT	UBRRL, 		S_Merker
	; Frame-Format: 8 Bit
	LDI	S_Merker, 	(1<<URSEL)|(3<<UCSZ0)
	OUT	UCSRC, 		S_Merker
	SBI	UCSRB, 		TXEN			; TX aktivieren (enable)
	SBI	UCSRB, 		RXCIE           	; Interrupt bei Empfang    
	SBI	UCSRB, 		RXEN 			; RX (Empfang) aktivieren (enable)
Ret

 
;------------------------------ Initialisieren der IO Ports----------------------------------
Init_Port:
	;PortB	als Eingang Bit 0 - 5
	ldi 	Temp, 	0b11000000	; lade Register mit Parametrierung 1 = Ausgang
	In 	Merker, DDRB		; Lade vorhandene Parameter
	AND	Temp, 	Merker      	; füge die Parametrierung der Eingänge hinzu
	out 	DDRB, 	Temp       	; schreibe neue Parameter ins IO-Register DDRB
	;Bit 6 und 7 behalten alte Parameter
	
	;PortC als Eingang Bit 0 - 5
	ldi 	Temp, 	0b11000000	; lade Register mit Parametrierung 1 = Ausgang
	In 	Merker, DDRC		; Lade vorhandene Parameter
	AND	Temp, 	Merker      	; füge die Parametrierung der Eingänge hinzu
	out 	DDRC, 	Temp       	; schreibe neue Parameter ins IO-Register DDRC
	;Bit 6 und 7 behalten alte Parameter

	; PortD	als Ausgang Bit 2 - 7
	ldi 	Temp, 	0b11111100	; lade Register mit Parametrierung 1 = Ausgang
	In 	Merker, DDRD		; Lade vorhandene Parameter
	OR	Temp, 	Merker      	; füge die Parametrierung der Ausgänge hinzu
	out 	DDRD, 	Temp       	; schreibe neue Parameter ins IO-Register DDRD 
	;Bit 0 und 1 behalten alte Parameter
RET

;------------------------------ Initialisieren der Register ----------------------------------
InitRegister:
	CLR	 Merker			; Register auf Startwert setzen
	CLR	 Temp
	CLR	 Ablage

	CLR	 Event_Reg
	CLR	 Referenz
	CLR	 Prg_Flags
	CLR	 ms0	
	CLR	 ms1	
	CLR	 ms2	
	CLR	 Sekunde
	CLR	 Minute
	CLR	 Stunde
	CLR	 Tag	
	CLR	 Woche
	CLR	 Tab_Cnt
	CLR	 Byte_Cnt
	CLR	 Cnt_Reg
	CLR	 S_Merker
	CLR	 T_Merker
	CLR	 Bef_Ctrl
	CLR	 Prg_Ctrl
	CLR	 Work_Reg


	CLR	 Send_Byte
RET
 

;------------------------------ Initialisieren der Variablen ----------------------------------
InitVariable:
	CLR	Temp
	STS	Erste_Variable, Temp		; Eintragen von Inhalt Register Temp
	STS 	Signal_Chg_1, Temp 		; in einzelne Variablen
	STS 	Neu_Input, Temp			; Wird benutzt, um in einem Programm  
	STS 	Old_Input, Temp			; gezielt Werte zurückzusetzen
	STS 	IO_Change_Bit, Temp 
	STS 	Change_To_Low, Temp
	STS 	Change_To_High, Temp
	STS 	Ausgabebyte, Temp
	STS 	Zaehlbyte, Temp 
	STS 	Bef_Control, Temp
;	STS 	Bef_Count, Temp
	STS 	Cnt_Soll, Temp
	STS 	Value_Dir, Temp 
	STS 	Write_Pos, Temp 
	STS 	Read_Pos, Temp 
	STS 	Zykl_Zeit_1, Temp
	STS 	Zykl_Zeit_2, Temp 
	STS 	Zykl_1, Temp
	STS 	Zykl_2, Temp 
RET
 
;---------------------- Vorbesetzen Variablenfeld ---------------------------------
Reset_Tabellen:
	CLR	Temp			; Ein Register auf 0 setzen
	LDI	ZH,High(Kanal_0)	; Adressregister mit Anfang der Tabelle laden
	LDI	ZL,Low(Kanal_0)
	LDI	Byte_Cnt, 20		; Ein Zählregister mit der Anzahl Bytes bestetzen
	RCall 	Clr_Tabelle		; Unterprogramm aufrufen. Dort wird Inhalt von Register
					; Temp in die Speicherzellen eingetragen
	LDI	ZH,High(Ring_Buf)
	LDI	ZL,Low(Ring_Buf)
	LDI	Byte_Cnt, 50		; Variabler Durchlauf durch Verwendung eines Zählregisters
	RCall 	Clr_Tabelle	
	LDI	ZH,High(Werte_Tab_1)
	LDI	ZL,Low(Werte_Tab_1)
	LDI	Byte_Cnt, 10
	RCall 	Clr_Tabelle	
	LDI	ZH,High(Werte_Tab_2)
	LDI	ZL,Low(Werte_Tab_2)
	LDI	Byte_Cnt, 10
	RCall 	Clr_Tabelle
	LDI	ZH,High(Werte_Tab_3)
	LDI	ZL,Low(Werte_Tab_3)
	LDI	Byte_Cnt, 10
	RCall 	Clr_Tabelle
	LDI	ZH,High(Werte_Tab_4)
	LDI	ZL,Low(Werte_Tab_4)
	LDI	Byte_Cnt, 10
	RCall 	Clr_Tabelle
	LDI	ZH,High(Quelltabelle)
	LDI	ZL,Low(Quelltabelle )
	LDI	Byte_Cnt, 45
	RCall 	Clr_Tabelle
	LDI	ZH,High(Zieltabelle)
	LDI	ZL,Low(Zieltabelle)
	LDI	Byte_Cnt, 45
	RCall 	Clr_Tabelle
RET


;-------------------- Tabellenreset ------------------------------------
Clr_Tabelle :
	ST	Z+, Temp
	Dec	Byte_Cnt
	BRNE	Clr_Tabelle		; wiederholt, wenn Byte_Cnt nicht 0
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)