Workshop: Programmierung in Assembler (Atmel ATM8)

Workshops von Usern dieses Forums angeboten.

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#76

Beitrag von Muenchner Kindl »

Hi,

mich wundert, dass hier der Compiler nicht mault:

ldi Eingang, pind ; Input nach Eingang schreiben

Wenn wir einen Port auslesen, dann bitte mit dem Kommando "IN", also in diesem Fall:

in Eingang, pind ; Input nach Eingang schreiben

Ob das Programm dann funktioniert weis ich allerdings nicht, das ist mir nur auf den ersten Blick aufgefallen ;)
Benutzeravatar

DeMorpheus
Metropolitan (MET)
Beiträge: 3632
Registriert: Mi 22. Dez 2010, 13:08
Nenngröße: H0
Steuerung: MS2 Gleisbox + BPi
Gleise: C-Gleis: ML+2L-fähig
Wohnort: Aachen
Alter: 30
Kontaktdaten:

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#77

Beitrag von DeMorpheus »

Danke Thomas,

ich wusste doch dass es was ganz einfaches war :D
Mit in statt ldi funktioniert das Codestückchen jetzt so wie gedacht, und ist für das Programm nicht brauchbar :lol:
Der Compiler hatte übrigens nichts daran auszusetzen ...

Schöne Grüße,

Moritz
Viele Grüße,
Moritz
'Nitwit! Blubber! Oddment! Tweak!'

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#78

Beitrag von Muenchner Kindl »

Guten Morgen,

nun habe ich "heimlich" eine Woche draufgelegt, dennoch habe ich nur eine Lösung zum Thema Taschenlampe erhalten. Nun, vielleicht ist das mit dem Stellen von Aufgaben doch nicht die gute Idee, ich werde heute etwas anderes versuchen.
Zunächst möchte ich auf unser Projekt "Taschenlampe" eingehen und hier ist schonmal sehr gut ersichtlich, dass sehr viele Wege nach Rom führen können.

Ich stelle dazu zwei Lösungen zur Diskussion, beide funktionieren und doch sind sie unterschiedlich aufgebaut.

Meine Lösung:

Code: Alles auswählen

.include "m8def.inc"      ; Damit weis der Compiler, auf welchen Proz er compilieren muss

.def Temp = r16
.def Muster = r17

.org 0x0000
        rjmp    Init                  	; Springe nach einem Reset zum Label "Init"


Init:									; Hier beginnt die Initialisierung


		; Setzen des Stackpointers
        ldi     r16, LOW(RAMEND)     	; unteres Byte der hächstmöglichen Adresse holen
        out     SPL, r16				; Unteres Byte des SP beschreiben
        ldi     r16, HIGH(RAMEND)		; oberes Byte der höchstmnöglichen Adresse holen
        out     SPH, r16				; oberes Byte des SP beschreiben

		; Initialisieren der Eingänge
		ldi r16, 0x00					; alle Bits in r16 auf 0 setzen
		out ddrd, r16					; Alle Pins von Port C sind Eingang

		ldi r16, 0xFF					; Bit 2 und Bit 3 setzen
		out portd, r16					; PC2 und PC3 bekommen PullUp-Widerstände

		ldi r16, 0xFF					; alle Bits in r16 auf 1 setzen
		out ddrb, r16					; Alle Pins von Port B sind Ausgang


		ldi Muster, 0x00				; Alle LEDs aus, Ausgangszustand
Schleife:

		sbis Pind, 2					; Ist T1 gedrückt?
		ldi Muster, 0xFF				; Wenn ja, alle LEDs ein
		sbis Pind, 3					; Ist T2 gedrückt?
		ldi Muster, 0x00				; Wenn ja, alle LEDs aus
		in 	Temp, PinD					; Port D in Temp laden
		andi Temp, 0b00001100			; Temp maskieren
		cpi Temp, 0x00					; Sind beide Taster gedrückt?
		brne Schleifenende				; Wenn nicht, dann Schleifenende mit Ausgabe
		ldi Muster, 0x55				; Wenn ja, dann jedes zweite Bit setzen

Schleifenende:
		out PortB, Muster				; Ausgabe des Leuchtmusters
		rjmp Schleife					; Zurück zum Anfang
Nun, mein Programm besteht aus einer einfachen Endlosschleife, innerhalb der drei Eingangszustände abgefragt werden. Entsprechend des Abfrageergebnisses wird jeweils das Bitmuster eingestellt und am Ende der Schleife ausgegeben. Das Programm ist kompakt und einfach gehalten, im Gegensatz zur folgenden Lösung allerdings eher unflexibel...:

Moritz's Lösung:

Code: Alles auswählen

.include "m8def.inc"      			; Damit weiss der Compiler, auf welchen Proz er kompilieren muss

.def Temp = r16
.def Lampe = r17

.org 0x0000
        rjmp    Init               	; Springe nach einem Reset zum Label "Init"


Init:                          	 	; Hier beginnt die Initialisierung

      ; Setzen des Stackpointers
        ldi     r16, LOW(RAMEND)    ; unteres Byte der hächstmöglichen Adresse holen
        out     SPL, r16            ; Unteres Byte des SP beschreiben
        ldi     r16, HIGH(RAMEND)   ; oberes Byte der höchstmnöglichen Adresse holen
        out     SPH, r16            ; oberes Byte des SP beschreiben

	  ; Initialisieren der Eingänge
	  	ldi Temp, 0x00				; alle Bits in r16 auf 0 setzen
		out ddrd, Temp				; alle Pins an Port D sind Eingang

		ldi Temp, 0xFF				; alle Bits in r16 setzen
		out portd, Temp				; PullUp-Widerstände für Port D

	  ; Initialisieren der Ausgänge
	    out ddrb, Temp				; alle Pins an Port B sind Ausgänge


Hauptprogramm:

		sbis pind, 2				; Bit 2 an Port D gesetzt?
		rjmp On						; Wenn nein, ist T 1 betaetigt, springe zu On
		sbis pind, 3				; Bit 3 an Port D gesetzt?
		rjmp Off					; Wenn nein, ist T 2 betaetigt, springe zu Off
		rjmp Hauptprogramm			; Schleife

On:

	  ; Kontrolle von T 2
	  	sbis pind, 3				; Bit 1 an Port D gesetzt?
		rjmp Half					; Wenn nein, ist T 2 auch betaetigt, springe zu Half

	  ; Temp beschreiben
	  	ldi Lampe, 0xFF				; Alle Bits in Lampe setzen
		out portb, Lampe			; Ausgabe von Lampe
		rjmp Hauptprogramm			; Springe zum Label Hauptprogramm

Off:

	  ; Temp beschreiben
	  	ldi Lampe, 0x00				; Alle Bits in Lampe auf null setzen
		out portb, Lampe			; Ausgabe von Lampe
		rjmp Hauptprogramm			; Springe zum Label Hauptprogramm

Half:

	  ; Temp beschreiben
	  	ldi Lampe, 0b01010101		; Jedes zweite Bit in Lampe setzen
		out portb, Lampe			; Ausgabe von Lampe
		rjmp Hauptprogramm			; Springe zum Label Hauptprogramm
Die ebenfalls funktionierende Lösung von Moritz sieht zunächst vergleichsweise umständlicher und größer aus. Er frägt nacheinander die beiden Taster ab und springt dann quasi in "Unterprogramme", in denen dann zusätzlich der jeweils andere Taster abgefragt wird, um dann das entsprechende Bitmuster einzustellen und auszugeben.

Da wir nun zwei unterschiedliche Lösungen haben stellt sich mal spasseshalber die Frage, welche die bessere ist... ;)

- Beide Programme dürften in etwa die gleichen Programmlaufzeiten haben. Während bei mir eine kurze Schleife durchlaufen wird kommt bei Moritz immer nur ein Ast zur Ausführung. Das nimmt sich nicht viel, u.U. ist mein Programm geringfügig aber unwesentlich schneller.

- Wer die Syntax beherrscht dürfte mit meiner Lösung schneller zurecht kommen. Ich musste bei Moritz schon ziemlich überlegen, wo er hinspringt, warum und was er da macht. In diesem Zusammenhang gibt es auch die scherzhafte Bezeichnung "Spaghetticode", die ich aber nicht, wie in Wikipedia beschrieben, abwertend meine.

- Moritz's Programm ist flexibler. Soll entsprechend der Taster etwas komplexeres als das blosse Einstellen eines Bitmusters passieren, dann lässt sich das in Moritz's Programm schnell und einfach innerhalb der "Unterprogramme" lösen. Im Prinzip hat Moritz auf das nächste größere Thema vorgegriffen. Das Abarbeiten diverser Funktionen in Unterprogrammen (Subroutines) ist generell die zu bevorzugende Lösung, wenngleich man das nicht mit Sprungbefehlen machen sollte. Da wir aber die Bedienung von Unterprogrammen noch nicht hatten möchte ich Moritz trotz "Spaghetticode" zur eigentlich besseren Lösung gratulieren ;)

Falls noch jemand eine Lösung hat, gerne auch eine nicht funktionierende, gerne auch Fragen... einfach her damit.

Ich werde heute eine Zeitschleife mit Euch "basteln", damit wir kommendes Wochenende das nächste Thema ansprechen können.

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#79

Beitrag von Muenchner Kindl »

Schleifen und verschachtelte Schleifen

kein wirklich neues Thema, vielmehr eine Vertiefung einiger bisher angesprochenen Funktionen sind Zeitschleifen.

Nicht nur bei Effekten kommt es darauf an, die für das Auge nicht wahrnehmbare Taktfrequenz von 8MHz herunterzuteilen. Um z.B. einen Ausgang sichtbar blinken zu lassen muss man das Ein/Ausschalten dieses Pins verzögern, wozu es, wie soll es anders sein ;) , mehrere Möglichkeiten gibt.

Die unvorteilhafteste von allen möchte ich hier besprechen. Einfach nur darum, um Euch noch ein weite Anwendung der bedingten Sprünge aufzuzeigen, aber auch um ein Sprungbrett zu den nächsten Themen zu haben.

Eine Aufgabe, die ich hier lösen will, ist die Realisierung eines Blinklichtes. LED1 (Pin B0) soll regelmäßig blinken. Dies ist übrigens eine Anwendung, die Ihr bereits in der Praxis verwenden könnt... Warnblinker, Baustellenblinker oder dergleichen. Das jetzt erarbeitete Programm bietet auch genügend Erweiterungsmöglichkeiten, dazu aber später mehr.

In der ersten Überlegung geht es also darum, einen Ausgang nacheinander ein- und wieder auszuschalten. Programmtechnisch würde das z.B. so aussehen:

Code: Alles auswählen

Blinklicht:
sbi portb, 0   ;LED1 einschalten
cbi portb, 0   ;LED1 ausschalten
rjmp Blinklich ;Zum Anfang
Die Initialisierung ect. habe ich weggelassen, Ihr könnt das aber gerne ausprobieren, dann aber bitte mit Initialisierung ;). Das Programm funktioniert, das "Blinken" könnt Ihr mit einem Oszilloskop oder Frequenzzähler darstellen. Sichtbar ist das Blinken jedoch nicht.
Es geht also darum, zwischen dem Ein- bzw. Ausschalten eine Zeit ablaufen zu lassen, etwa so:

Code: Alles auswählen

Blinklicht:
sbi portb, 0   ;LED1 einschalten
; 1 Sekunde warten
cbi portb, 0   ;LED1 ausschalten
; 1 Sekunde warten
rjmp Blinklich ;Zum Anfang
Nun ist es so, dass jeder Befehl eine gewisse Anzahl von Takten für zur Abarbeitung beansprucht und so wäre es möglich, ganz viele Befehle hintereinander zwischen das Ein/Ausschalten einzufügen.
Wenn Ihr Euch das Instruction Set anschaut, dann findet Ihr bei jedem Befehl die Anzahl der Zyklen (Cycles), die zur Abarbeitung benötigt werden. Da wir für die Zeitverzögerung keine "sinnvollen" Befehle benötigen schauen wir uns mal das Kommando NOP (No Operation) auf Seite 108 an:
Dieser Befehl macht nichts, ausser den Programmzähler um 1 nach oben zu zählen und dafür einen Taktzyklus zu beanspruchen. Bei 8MHz dauert das Abarbeiten eines NOP-Befehls 0,000000125 Sekunden und so müssten wir zwischen dem Ein/Ausschalten jeweils 8 Millionen NOP-Befehle einfügen, um eine Blinkfrequenz von einer Sekunde zu erreichen. Dass das wenig Freude macht dürfte klar sein, wir müssen also eine elegantere Lösung suchen, um etwa 8 Millionen Zyklen zu erreichen und hier bieten sich Zeitschleifen an:

Code: Alles auswählen

Blinklicht:
sbi portb, 0   ;LED1 einschalten

Schleife1:
inc Zeit1       ;Zeit 1 hochzählen
cpi Zeit1, 0xFF ;255 Zyklen erreicht?
brne Schleife1 ;wenn nein, dann Schleife wiederholen

cbi portb, 0   ;LED1 ausschalten

Schleife2:
inc Zeit1       ;Zeit 1 hochzählen
cpi Zeit1, 0xFF ;255 Zyklen erreicht?
brne Schleife2 ;wenn nein, dann Schleife wiederholen

rjmp Blinklich ;Zum Anfang
Ich habe also zwischen dem Toggeln (=umschalten) des Pins jeweils eine Schleife eingebaut, die bis 255 zählt. Das sind etwas mehr als 255 Takte (jeder der Befehle innerhalb der Schleife benötigt seine Takte). Man kann sich mit Hilfe des Befehlshandbuchs ziemlich exakt die Laufzeit einer Schleife errechnen, wir legen hier keinen grossen Wert auf Genauigkeit. Wir wollen einfach nur ein sichtbares Blinken und mit etwa 255x0,000000125=0,000031875 Sekunden wird das auch nicht wirklich der Fall sein.
Wir müssen also die Schleife weiter verschachteln und so dafür sorgen, dass diese mehrmals ausgeführt wird, damit 8000000 Zyklen damit durchlaufen werden, bevor der Ausgang seinen Zustand ändert. Leider sind wir auf die 8 Bit eines Registers und damit auf eine maximale Anzahl von 255 Durchläufen pro Schleife begrenzt, und so müssen wir tricksen:

Code: Alles auswählen

Schleife:
inc Zeit1          ;Zeit1 um 1 erhöhen
cpi Zeit1, 0xFF ;Sind 255 Zyklen für Zeit1 erreicht?
brne Schleife    ;Wenn nein, weiterer Durchlauf

inc Zeit2          ;Zeit2 um 1 erhöhen
cpi Zeit2, 0xFF ;Sind 255 Zyklen für Zeit2 erreicht?
brne Schleife    ;Wenn nein, dann nochmal alles von vorne
Ich habe hier zwei Schleifen verschachtelt. Ist die eine (Zeit1) durchgelaufen, dann wird die Zeitvariable der zweiten Schleife (Zeit2) um 1 erhöht. Erst wenn diese auch 255 mal durchlaufen ist wird die Schleife verlassen. Damit haben wir also etwa 255+255=65025 Zyklen und damit kommen wir den 8Mio schon recht nahe. Es ist also erforderlich, diese verschachtelte Schleife nochmal zu verschachteln, damit das Gebilde 8000000/65025=123 mal durchlaufen wird, damit wir eine Verzögerung von etwa 1sec erhalten.

Code: Alles auswählen

Schleife:
inc Zeit1          ;Zeit1 um 1 erhöhen
cpi Zeit1, 0xFF ;Sind 255 Zyklen für Zeit1 erreicht?
brne Schleife    ;Wenn nein, weiterer Durchlauf

inc Zeit2          ;Zeit2 um 1 erhöhen
cpi Zeit2, 0xFF ;Sind 255 Zyklen für Zeit2 erreicht?
brne Schleife    ;Wenn nein, dann nochmal alles von vorne

inc Zeit3          ;Zeit3 um 1 erhöhen
cpi Zeit3, 0x7B ;Sind 123 Durchgänge erreicht?
brne Schleife    ;Wenn nein, dann nochmal alles von vorne

ldi Zeit3, 0x00  ;Zeit3 löschen!
Wir haben also ein Schleifengebilde, welches in der Theorie 255x255x123=7998075 mal durchlaufen wird, damit erreichen wir eine Verzögerung von etwa einer Sekunde zwischen dem Toggeln. Wichtig ist, dass die Zeitvariable der äussersten Zeitschleife gelöscht wird, wenn die Schleife verlassen wird. Dies gilt auch für alle anderen Zeitvariablen, welche nicht bis 255 laufen sollen.

Hier also das fertige und funktionsfähige Programm zum Ausprobieren und als Experimentiergrundlage:

Code: Alles auswählen

; Blinklicht mit verschachtelten Schleifen

.include "m8def.inc"      ; Damit weis der Compiler, auf welchen Proz er compilieren muss

.def Time1 = r16
.def Time2 = r17
.def Time3 = r18

.org 0x0000
        rjmp    Init                  	; Springe nach einem Reset zum Label "Init"


Init:									; Hier beginnt die Initialisierung


		; Setzen des Stackpointers
        ldi     r16, LOW(RAMEND)     	; unteres Byte der hächstmöglichen Adresse holen
        out     SPL, r16				; Unteres Byte des SP beschreiben
        ldi     r16, HIGH(RAMEND)		; oberes Byte der höchstmnöglichen Adresse holen
        out     SPH, r16				; oberes Byte des SP beschreiben

		; Initialisieren der Eingänge
		ldi r16, 0x00					; alle Bits in r16 auf 0 setzen
		out ddrd, r16					; Alle Pins von Port C sind Eingang

		ldi r16, 0xFF					; Bit 2 und Bit 3 setzen
		out portd, r16					; PC2 und PC3 bekommen PullUp-Widerstände

		ldi r16, 0xFF					; alle Bits in r16 auf 1 setzen
		out ddrb, r16					; Alle Pins von Port B sind Ausgang


		ldi r16, 0x00					; Alle Bits in r16 löschen
		out portb, r16					; Alle LEDs aus


Schleife1:
		inc Time1						; Time1 um 1 erhöhen
		cpi Time1, 0xFF					; Ist der Wert 255 erreicht?
		brne Schleife1					; Wenn nein, dann Schleifenanfang
		

		inc Time2						; Time2 um 1 erhöhen
		cpi Time2, 0xFF					; Ist der Wert 255 erreicht?
		brne Schleife1					; Wenn nein, dann Schleifenanfang
		

		inc Time3						; Time3 um 1 erhöhen
		cpi Time3, 0x7B					; Ist der Wert 123 erreicht?
		brne Schleife1					; Wenn nein, dann Schleifenanfang

		ldi Time3, 0x00					; Time3 zurücksetzen
		sbi PortB, 0					; LED1 einschalten


Schleife2:
		inc Time1						; Time1 um 1 erhöhen
		cpi Time1, 0xFF					; Ist der Wert 255 erreicht?
		brne Schleife2					; Wenn nein, dann Schleifenanfang
		

		inc Time2						; Time2 um 1 erhöhen
		cpi Time2, 0xFF					; Ist der Wert 255 erreicht?
		brne Schleife2					; Wenn nein, dann Schleifenanfang
		

		inc Time3						; Time3 um 1 erhöhen
		cpi Time3, 0x7B					; Ist der Wert 123 erreicht?
		brne Schleife2					; Wenn nein, dann Schleifenanfang

		ldi Time3, 0x00                                  ; Zeitvariable löschen!
		cbi PortB, 0					; LED1 ausschalten
                

		rjmp Schleife1					; Zurück zu Schleife1
Wenn Ihr das in ein neues Projekt kopiert, kompiliert und in den ATM lädt sollte LED1 im Sekundentakt blinken... oder?
Bevor Ihr entteuscht seid... die LED1 wird blinken und sie wird sichtbar blinken, aber von 1 Sekunde werden wir ein Stück weit entfernt sein. Warum das so ist dürft Ihr gerne herausfinden und hier posten bzw. diskutieren.
Nur so viel, mit der Angabe der Zyklen bei jedem Befehl liesse sich so eine Zeitschleife ziemlich exakt berechnen. Wir, bzw. ich habe hier vieles nicht berücksichtigt aber wenn Ihr das Prinzip verinnerlicht habt kommt Ihr sicher auf meinen Fehler. Ihr dürft auch gerne mit dem Programm und den Zeitvariablen experimentieren, Fragen werden selbstverständlich beantwortet, idealerweise in diesem Thread. Wer will darf sich mit diesen Mitteln auch gerne eine exakte 1sec-Schleife zusammenrechnen und realisieren. Möglich ist das :-)

Viel Spass beim Experimentieren ;) kommendes Wochenende geht es weiter.

Achja, wie schon oben erwähnt ist dieses Projekt bereits in der Lage, Anlagentauglich gemacht zu werden. Ihr dürft gerne mit den Zeiten spielen und könnt auch gerne zusätzlich andere Ausgänge ansteuern. Baustellenblinker, Wechselblinker ect. sind also realisierbar. Wer was praktikables hat... her damit :D

Ein Nachtrag noch zu einer Anmerkung ganz am Anfang:
Die unvorteilhafteste von allen
Mit "unvorteilhaft" ist nicht die verschachtelte Schleife an sich gemeint. Um diese werden wir, geht es um zeitliche Abläufe, kaum herumkommen. Unvorteilhaft ist aber die Integration im Programm. Als Verzögerungsglied zwischen zwei Funktionen unverzichtbar, als zuverlässige Zeitsteuerung unbrauchbar, da sie nur an bestimmten Stellen des Programms ausgeführt wird.
Andere Möglichkeiten, Zeitschleifen auszuführen, kommen mit den nächsten beiden Themen ;-)

Und noch was:
Wie Ihr seht haben wir bereits einiges an Handwerkszeug beisammen. Wir sind jetzt schon in der Lage, kleine Steuerungen oder Effekte zu realisieren. An dieser Stelle sei gesagt, dass von meiner Seite nicht mehr wahnsinnig viel Befehle dazukommen werden. Klar, ein paar werden es schon sein, aber es wird immer mehr in die Richtung gehen, das bisher Erlernte zur Anwendung zu bringen. Assembler ist keine Programmiersprache mit einem grossen "Wortschatz". Auch wenn der Befehlssatz eines ATM8 recht beachtlich ist, mit wenigen Befehlen lassen sich bereits komplexe Probleme lösen.
So wird unsere Zeitschleife bereits ein elementarer Bestandteil unserer Ampel sein ;-)
Benutzeravatar

DeMorpheus
Metropolitan (MET)
Beiträge: 3632
Registriert: Mi 22. Dez 2010, 13:08
Nenngröße: H0
Steuerung: MS2 Gleisbox + BPi
Gleise: C-Gleis: ML+2L-fähig
Wohnort: Aachen
Alter: 30
Kontaktdaten:

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#80

Beitrag von DeMorpheus »

Guten Abend :wink: ,

die Schleife ist toll! Man kann ja damit nicht nur das Blinklicht machen, sondern ohne großen Aufwand auch Wechselblinker oder Lauflichter und ähnliches.
So langsam nähern wir uns den sinnvollen Anwendungen :sabber:

Die Sache mit der Blinkfrequenz lässt sich ja auch recht einfach erklären. Nur mit der genauen Berechnung hapert's ein wenig: Ich hab mir die Zyklen ausgerechnet, durch die Taktfrequenz geteilt und etwa 10% Abweichung zum selbst Gesehenen gehabt. Rückgerechnet hätte mein Microcontroller eine Taktfrequenz von 8,7 MHz. Da ich das nicht glaube, muss meine Berechnung falsch sein. Ich knoble noch :P

Was machen denn all die anderen Teilnehmer so? Wie sieht's aus?

Schöne Grüße,

Moritz
Viele Grüße,
Moritz
'Nitwit! Blubber! Oddment! Tweak!'
Benutzeravatar

kaeselok
ICE-Sprinter
Beiträge: 6785
Registriert: Mo 30. Apr 2007, 13:15
Nenngröße: 1
Stromart: digital
Steuerung: ECoS II / TAMS
Gleise: Hübner nach NEM
Wohnort: Brühl

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#81

Beitrag von kaeselok »

DeMorpheus hat geschrieben:Was machen denn all die anderen Teilnehmer so? Wie sieht's aus?
Ich drucke alles aus und lese es später, wenn ich dafür etwas Zeit finde :) .

In der Tat nähern wir uns nun interessanten Anwendungen, Baustellenblitz z.B. :D

Viele Grüße,

Kalle

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#82

Beitrag von Muenchner Kindl »

Guten Morgen,
Die Sache mit der Blinkfrequenz lässt sich ja auch recht einfach erklären. Nur mit der genauen Berechnung hapert's ein wenig: Ich hab mir die Zyklen ausgerechnet, durch die Taktfrequenz geteilt und etwa 10% Abweichung zum selbst Gesehenen gehabt. Rückgerechnet hätte mein Microcontroller eine Taktfrequenz von 8,7 MHz. Da ich das nicht glaube, muss meine Berechnung falsch sein. Ich knoble noch
Es sind mehrere Dinge zu beachten:
Zum Einen geht es um die Kommandos selbst. Ich war, um es einfach und trivial zu halten, davon ausgegangen, dass pro Schleifendurchlauf ein Takt abgearbeitet wird, was völliger Unfug ist. Jedes Kommando hat seine Anzahl an Takten und jeder nicht berücksichtigte Takt potenziert sich natürlich nach oben. Bei den bedingten Sprungbefehlen kommt es auch noch darauf an, ob die Bedingung erfüllt ist oder nicht, man kann sich also wirklich lange damit beschäftigen... oder auch nicht ;) . Vermutlich kommt man mit "Näherung" sogar schneller ans Ziel oder zumindest in die Nähe.

Zum anderen haben wir den internen Oszillator des ATM. Der ist alles andere als genau, unkallibriert eher ein Schätzeisen. Wer es genau haben will muss seinen Prozessor kallibrieren oder, noch besser, extern mit einem Quarz oder Quarzoszillator takten.
Zum Thema Kallibrieren gehe ich heute Abend nochmal kurz ein, dazu brauche ich einen Screenshot.
So langsam nähern wir uns den sinnvollen Anwendungen
Wie schon geschrieben haben wir bereits ein relativ effektives Handwerkszeug. Auch wenn noch ein paar Werkzeuge fehlen, im Prinzip geht es im weiteren Verlauf nur noch darum, dieses für verschiedene Dinge einzusetzen. Es kommt viel Denk- und Knobelarbeit hinzu aber im wesentlichen können wir bereits das eine oder andere umsetzen. Auf jeden Fall werden wir demnächst unsere erste Ampelsteuerung haben... zwar nicht die endgültige aber zumindest eine, die funktioniert ;)

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#83

Beitrag von Muenchner Kindl »

Hallo,

eigentlich nicht vorgesehen aber wenn Interesse besteht habt Ihr hier eine kleine Zugabe. Es geht um das Kalibrieren des internen Oszillators, was ich hier kurz beschreibe.

Wie schon weiter oben beschrieben taktet der interne Oszillator nicht gerade genau. Wer auf exakte Frequenzen angewiesen ist muss mit einem externen Quarz oder mit einem präzieseren Oszillator takten. Ein gangbarer Weg, wenn es doch nicht so 100%ig sein muss ist das Kalibrieren des internen Oszillators.

Die exakte zu taktende Frequenz wird zur Laufzeit in das Register OSCCAL geschrieben. Dazu muss diese erst, leider vorher, ermittelt werden.
Klick dazu bitte auf das rechte der beiden Chipsymbole, auf das Ihr auch zum Beschreiben des Prozessors klickt. Aktiviert dann den Reiter "Advanced", dann solltet Ihr folgenden Dialog sehen:
Bild
Bei "Calibrate for frequency" wählt Ihr bitte 8MHz aus und klickt auf "Read". Was dann in "Value" steht ist der Wert, mit dem der Oszillator kalibriert werden kann.
Ermittelt diesen Wert bitte bei Zimmertemperatur, bzw. in der Umgebung, wo der Prozessor eingesetzt werden soll. Mit diesem Satz erkennt Ihr bereits das Problem dieser Methode. Wenn ich den Wert heute Nacht bei Frost auf dem Balkon ermittle wird die Schaltung morgen im warmen Büro vermutlich nicht mehr funktionieren.

Nun habe ich für meinen Prozessor in meiner Umgebung den Wert 0xA3 ermittelt (bei Euch wird das sicher ein anderer Wert sein!), dieser muss nun in das OSCCAL-Register, idealerweise bei der Initialisierung:

Code: Alles auswählen

ldi r16, 0xA3      ;Kalibrierwert f. int. Oszillator
out osccal, r16   ;Kalibrierwert in OSCCAL schreiben
Leider lässt sich der Wert nicht zur Laufzeit ermitteln und so muss die Ermittlung währen der Entwicklung in geeigneter Umgebung (Temperatur) geschehen.

Ihr dürft das gerne für Euere Programme machen, ich werde aber darauf verzichten. So exakt muss unsere Ampel nicht sein und es ist für Euch einfacher, meine Codes zu übernehmen.
Benutzeravatar

DeMorpheus
Metropolitan (MET)
Beiträge: 3632
Registriert: Mi 22. Dez 2010, 13:08
Nenngröße: H0
Steuerung: MS2 Gleisbox + BPi
Gleise: C-Gleis: ML+2L-fähig
Wohnort: Aachen
Alter: 30
Kontaktdaten:

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#84

Beitrag von DeMorpheus »

Muenchner Kindl hat geschrieben:Wie schon weiter oben beschrieben taktet der interne Oszillator nicht gerade genau.
Hui!
Nach der Kalibrierung stimmt die von mir berechnete Blinkfrequenz ziemlich genau. Dann lief mein Oszi also wirklich mit 8,7 statt 8 MHz. Das ist schon heftig ... aber wirklich ziemlich egal. Danke für die Zugabe :)

Schöne Grüße,
Moritz
Viele Grüße,
Moritz
'Nitwit! Blubber! Oddment! Tweak!'

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#85

Beitrag von Muenchner Kindl »

Unterprogramme

Bevor es losgeht erstmal was organisatorisches:
Es bringt keinen Sinn, Aufgaben zu stellen, die dann keiner macht. Klingt jetzt doof nach Schule, ist aber nicht so gemeint. Mir ist durchaus bewusst, dass es nicht immer möglich ist, für sowas die Zeit zu finden und wenn, dann nutzt man die vielleicht doch eher zur Erholung.
Nun werde ich das so gestalten, dass ich quasi uns eine Aufgabe stelle und diese hier öffentlich, idealerweise mit Hilfe des eigentlichen Themas, löse. Euere "Aufgabe" besteht dann lediglich darin, mit der Lösung zu experimentieren, diese zu verbessern oder Eueren eigenen Wünschen anzupassen. Wie wir schon bemerkt haben kommen wir langsam aber sicher dazu, brauchbare Anwendungen zu realisieren und eine davon ist das heutige Thema:

Verkehrsampel!

Gefordert ist eine einfache, automatische, Verkehrsampel, wie sie z.B. an einer befahrenen Kreuzung stehen könnte. Die Rot- und Grünphase der jeweiligen Strassen sind identisch, eine Fussgängerampel brauchen wir jetzt nicht, darf aber gerne mit realisiert werden.

Wir legen folgendes fest:
LED1 = rot 1
LED2 = gelb 1
LED3 = grün 1

LED6 = rot 2
LED7 = gelb 2
LED8 = grün 2

Eingänge haben wir keine, nach dem Einschalten soll die Ampel einfach vor sich hin arbeiten.

Nun kennen wir die Anforderung und ich kann Euch sagen, dass wir diese mit den bisherigen Kenntnissen vollumfänglich lösen können. Letztendlich geht es nur darum, verschiedene Bitmuster nacheinander mit einem bestimmten Zeitabstand auszugeben. Vereinfacht und mit eigenen Worten sieht das dann so aus:

- Strasse1 rot, Strasse2 grün
- lange Zeitschleife
- Strasse1 rot/gelb, Strasse2 gelb
- kurze Zeitschleife
- Strasse1 grün, Strasse2 rot
- lange Zeitschleife
- Strasse1 gelb, Strasse2 rot/gelb
- kurze Zeitschleife
- Sprung zum Anfang->

Die Ausgabe der Bitmuster geht sicher mit LDI und OUT locker vom Hocker und auch die Zeitschleifen sind dank Copy and Paste kein Problem. Für die Bitmuster brauchen wir a' 2 Kommandos, die Zeitschleifen kopieren wir uns einfach zwischen jedes Bitmuster und passen das an. Das können wir bereits, ist aber erstens nicht sehr elegant, wird ein riesen Programm und übersichtlich ist das auch nicht.
Wenn Ihr Euch mein Wortprogramm so anschaut fallen Euch sicher ein paar Wiederholungen auf. Es gibt mehrere "kurze Zeitschleife" und ebensoviele "lange Zeitschleife". Wäre es nicht praktisch, einfach nur eine kurze und eine lange Zeitschleife zu schreiben und diese bei Bedarf aufzurufen?
Schauen wir mal ein paar Beiträge zurück, vor allem auf die Lösung von Moritz. Er hat sein Programm quasi schon in eine Art Module aufgeteilt, allerdings musste er diese immer mit Sprungbefehlen anfahren, was uns nichts hilft. Wir müssen die Module an mehreren Stellen aufrufen und haben damit jedesmal andere Rücksprungadressen, das lässt sich jedoch mit Spaghetticode nicht umsetzen.

Was wir brauchen ist ein Sprungbefehl, der sich die Rücksprungadresse merkt und genau den will ich Euch heute vorstellen: Erwähnt hatten wir ihn ja schonmal, heute wenden wir ihn praktisch an: RCALL und RET

Um ein Modul, also ein Unterprogramm aufzurufen brauchen wir also den Befehl

Code: Alles auswählen

rcall Label
Dieser legt die nachfolgende Adresse in den Stack und lädt dann die durch das Label angegebene Adresse in den Programmzähler. Damit wird also zu Beginn des Unterprogrammes weitergearbeitet.
Am Ende des Unterprogramms steht der Befehl welcher die im Stack im Zugriff befindliche Adresse in den Programmzähler holt. Wenn sämtliche Stackoperationen korrekt eingebunden und ausgeführt wurden ist das die Adresse, die der Befehl RCALL vorher auf den Stapel gelegt hat und so springt das Programm damit an die Adresse zurück die nach dem Aufruf des Unterprogramms kommt.

So, damit reicht es also, zwei unterschiedliche Zeitschleifen zu schreiben und diese beliebig wie ein Modul aufzurufen.
Hier die funktionierende Lösung:

Code: Alles auswählen

; automatische Verkehrsampel

.include "m8def.inc"      ; Damit weis der Compiler, auf welchen Proz er compilieren muss

.def Time1 = r16
.def Time2 = r17
.def Time3 = r18
.def Temp = r19

.org 0x0000
        rjmp    Init                     ; Springe nach einem Reset zum Label "Init"


Init:                           ; Hier beginnt die Initialisierung


      ; Setzen des Stackpointers
        ldi     r16, LOW(RAMEND)        ; unteres Byte der hächstmöglichen Adresse holen
        out     SPL, r16            ; Unteres Byte des SP beschreiben
        ldi     r16, HIGH(RAMEND)      ; oberes Byte der höchstmnöglichen Adresse holen
        out     SPH, r16            ; oberes Byte des SP beschreiben

      ; Initialisieren der Eingänge
      ldi r16, 0x00               ; alle Bits in r16 auf 0 setzen
      out ddrd, r16               ; Alle Pins von Port C sind Eingang

      ldi r16, 0xFF               ; Bit 2 und Bit 3 setzen
      out portd, r16               ; PC2 und PC3 bekommen PullUp-Widerstände

      ldi r16, 0xFF               ; alle Bits in r16 auf 1 setzen
      out ddrb, r16               ; Alle Pins von Port B sind Ausgang


      ldi r16, 0x00               ; Alle Bits in r16 löschen
      out portb, r16               ; Alle LEDs aus



Hauptprogramm:
	ldi Temp, 0b10000001		; S1=rot, S2=grün
	out PortB, Temp				; Ausgabe Bitmuster
	rcall Lang					; Lange Zeitschleife

	ldi Temp, 0b11000010		; S1=rot/gelb, S2=gelb
	out PortB, Temp				; Ausgabe Bitmuster
	rcall Kurz					; Kurze Zeitschleife

	ldi Temp, 0b00100100		; S1=grün, S2=rot
	out PortB, Temp				; Ausgabe Bitmuster
	rcall Lang					; Lange Zeitschleife

	ldi Temp, 0b01000110		; S1=gelb, S2=rot/gelb
	out PortB, Temp				; Ausgabe Bitmuster
	rcall Kurz					; Kurze Zeitschleife

	rjmp Hauptprogramm			; Zurück zum Anfang







Kurz:
      inc Time1              	; Time1 um 1 erhöhen
      cpi Time1, 0xFF           ; Ist der Wert 255 erreicht?
      brne Kurz            		; Wenn nein, dann Schleifenanfang
      

      inc Time2             	; Time2 um 1 erhöhen
      cpi Time2, 0xFF               ; Ist der Wert 255 erreicht?
      brne Kurz               	; Wenn nein, dann Schleifenanfang
      

      inc Time3        		   	 ; Time3 um 1 erhöhen
      cpi Time3, 0x30  	        ; Ist der Wert 0x30 erreicht?
      brne Kurz           		; Wenn nein, dann Schleifenanfang

      ldi Time3, 0x00     		; Time3 zurücksetzen
      
	  ret						; Zurück


Lang:
      inc Time1                  ; Time1 um 1 erhöhen
      cpi Time1, 0xFF            ; Ist der Wert 255 erreicht?
      brne Lang             ; Wenn nein, dann Schleifenanfang
      

      inc Time2                  ; Time2 um 1 erhöhen
      cpi Time2, 0xFF            ; Ist der Wert 255 erreicht?
      brne Lang             ; Wenn nein, dann Schleifenanfang
      

      inc Time3                  ; Time3 um 1 erhöhen
      cpi Time3, 0xA0            ; Ist der Wert 0xA0 erreicht?
      brne Lang					; Wenn nein, dann Schleifenanfang

      ldi Time3, 0x00            ; Zeitvariable löschen!

	  ret						; Zurück
		
Achtet bitte auf das kurze Hauptprogramm und wer sich das aufmerksam anschaut wird feststellen, dass ich einfach die beiden Zeitschleifen des vorherigen Themas kopiert, umbenannt und als Unterprogramme eingebunden habe, ja ich bin faul ;)

Das ganze hat entscheidende Vorteile:
Nicht nur, dass das Programm in sich übersichtlicher zu gestalten ist, elementare Änderungen, z.B. wenn die Rot/Grünphase zu korrigieren ist, müssen nur an einer Stelle gemacht werden. Letzteres kann jedoch ein Nachteil sein, wenn für unser Beispiel zwei unterschiedliche Rot/Grün-Phasen gefordert sind, z.B. für eine Kreuzung von Haupt- und Nebenstrasse. Dies wäre jedoch einfach mit einer dritten Zeitschleife, z.B. "Lang2" fix zu realisieren, Ihr seid gerne zum Experimentieren eingeladen.

Ich lasse Euch jetzt eine Woche damit in Ruhe, stehe aber selbstverständlich für Fragen zur Verfügung. Bitte versucht die ganze Geschichte, insbesondere auch die Thematik mit dem Stack, zu verinnerlichen.
Bitte experimentiert ein wenig, vielleicht fällt Euch noch etwas ein, was einer Ampel fehlt. Ihr könnt das Programm theoretisch und auch praktisch bereits auf der Anlage oder einem Modul einsetzen ;)

Viel Spass, bis zum nächsten Thema
Benutzeravatar

samson
InterRegioExpress (IRE)
Beiträge: 373
Registriert: Di 12. Mai 2009, 13:53
Nenngröße: H0
Stromart: AC
Steuerung: CS2 CS1r CdB
Gleise: Vitrine ;-)
Wohnort: Rheinhessen

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#86

Beitrag von samson »

Hallo,

und entschuldigung, ich konnte leider nicht früher zu dem spannenden Thema zurückkehren, da mein Chef meinte ich müsste die Jahressadistik äh Jahresstatistik gemeinsam mit einem Kollegen machen der wie ich sich erst in die Materie einarbeiten musste :evil:

Nun aber hoffentlich etwas ausdauernder :oops:

Ihr habt ja schon einiges weiter geschafft; das habe ich quergelesen, aber leider die Übungen noch nicht machen geschweige denn die Lösungen nachvollziehen können. Aber hoffe das die nächsten Tage zu tun.
Gruß
Christoph

CS3+ 1.2.0 // CS2 4.1.0 // CS1 Aufgeladen 4.1.0 // MS2 // MS1 // 6021 // CAN-digital-Bahn by TM // Vitrinenbahner // Ausprobierer

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#87

Beitrag von Muenchner Kindl »

Hi Christoph,
Ihr habt ja schon einiges weiter geschafft; das habe ich quergelesen, aber leider die Übungen noch nicht machen geschweige denn die Lösungen nachvollziehen können. Aber hoffe das die nächsten Tage zu tun.
Da mir seit einiger Zeit die Migräne und Schlaflosikeit fast jedes Wochenende versaut kommt es mir ganz gelegen, dass seit dem letzten Thema kein Feedback kam. Ich werde also noch eine Woche warten bis es mit den Interupts weitergeht, uns treibt ja keiner an.

Wenn es Fragen ect. gibt... immer her damit!
Benutzeravatar

samson
InterRegioExpress (IRE)
Beiträge: 373
Registriert: Di 12. Mai 2009, 13:53
Nenngröße: H0
Stromart: AC
Steuerung: CS2 CS1r CdB
Gleise: Vitrine ;-)
Wohnort: Rheinhessen

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#88

Beitrag von samson »

Hallo Thomas,

na dann mal gute Besserung auf das kein Föhn kommen mag die nächste Zeit.

Habe die Programme mal ausprobiert und nachvollzogen und den einen oder anderen Parameter variiert, hat gut geklappt.

Allerdings habe ich keinen Sekundentakt im Blinklicht, sondern etwas viel länger. Ich habe ja AVR Studio5 installiert, da kann ich die Oszillatorfrequenz irgendwie nicht so überprüfen wie mit der Version 4. Es gibt kein Register Advanced. Wie kann ich sie anders testen und entsprechend korrigieren?

Noch eine anderer Punkt: warum teile ich den Stack in einen oberen und unteren Bereich ein? Damit er leichter adressierbar ist doch eigentlich nicht. Sondern? Welchen Vorteil habe ich damit gewonnen?
Gruß
Christoph

CS3+ 1.2.0 // CS2 4.1.0 // CS1 Aufgeladen 4.1.0 // MS2 // MS1 // 6021 // CAN-digital-Bahn by TM // Vitrinenbahner // Ausprobierer

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#89

Beitrag von Muenchner Kindl »

Guten Abend,
Allerdings habe ich keinen Sekundentakt im Blinklicht, sondern etwas viel länger.
Wenn Du meinen Code genommen hast, dann ist das so richtig. Ich habe vieles nicht berücksichtigt.
Ich habe ja AVR Studio5 installiert, da kann ich die Oszillatorfrequenz irgendwie nicht so überprüfen wie mit der Version 4. Es gibt kein Register Advanced. Wie kann ich sie anders testen und entsprechend korrigieren?
Habe etwas gegoogelt, leider erfolglos. Vielleicht bringt Dir die Hilfefunktion der Software etwas, mal nach "calibrate for frequency" suchen.
warum teile ich den Stack in einen oberen und unteren Bereich ein? Damit er leichter adressierbar ist doch eigentlich nicht.
Du meinst sicher:

Code: Alles auswählen

      ; Setzen des Stackpointers
        ldi     r16, LOW(RAMEND)        ; unteres Byte der hächstmöglichen Adresse holen
        out     SPL, r16            ; Unteres Byte des SP beschreiben
        ldi     r16, HIGH(RAMEND)      ; oberes Byte der höchstmnöglichen Adresse holen
        out     SPH, r16            ; oberes Byte des SP beschreiben
Der Stackpointer ist ein 16-Bit-Register. Leider kann der Befehl Out keine 16 Bit transportieren, also müssen die unteren 8 Bit (SPL) und die oberen 8 Bit (SPH) separat in den Stackpointer geschrieben werden.
Benutzeravatar

samson
InterRegioExpress (IRE)
Beiträge: 373
Registriert: Di 12. Mai 2009, 13:53
Nenngröße: H0
Stromart: AC
Steuerung: CS2 CS1r CdB
Gleise: Vitrine ;-)
Wohnort: Rheinhessen

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#90

Beitrag von samson »

Hallo Thomas,
Muenchner Kindl hat geschrieben:Guten Abend,
Allerdings habe ich keinen Sekundentakt im Blinklicht, sondern etwas viel länger.
Wenn Du meinen Code genommen hast, dann ist das so richtig. Ich habe vieles nicht berücksichtigt.
Aha, gut zu wissen. Also kann ich die eine oder andere Schleife rausnehmen?
Muenchner Kindl hat geschrieben:
Ich habe ja AVR Studio5 installiert, da kann ich die Oszillatorfrequenz irgendwie nicht so überprüfen wie mit der Version 4. Es gibt kein Register Advanced. Wie kann ich sie anders testen und entsprechend korrigieren?
Habe etwas gegoogelt, leider erfolglos. Vielleicht bringt Dir die Hilfefunktion der Software etwas, mal nach "calibrate for frequency" suchen.
Habe ich auch schon probiert, kein Ergebnis, selbst wenn die die Online Hilfe aus dem Netz aktiviere. Ich lasse das mal erst so und installiere parallel dazu mal AVR Studio 4 und schaue an wie es damit aussieht.
Muenchner Kindl hat geschrieben:
warum teile ich den Stack in einen oberen und unteren Bereich ein? Damit er leichter adressierbar ist doch eigentlich nicht.
Du meinst sicher:

Code: Alles auswählen

      ; Setzen des Stackpointers
        ldi     r16, LOW(RAMEND)        ; unteres Byte der hächstmöglichen Adresse holen
        out     SPL, r16            ; Unteres Byte des SP beschreiben
        ldi     r16, HIGH(RAMEND)      ; oberes Byte der höchstmnöglichen Adresse holen
        out     SPH, r16            ; oberes Byte des SP beschreiben
Der Stackpointer ist ein 16-Bit-Register. Leider kann der Befehl Out keine 16 Bit transportieren, also müssen die unteren 8 Bit (SPL) und die oberen 8 Bit (SPH) separat in den Stackpointer geschrieben werden.
Ah nun ist es klar. Danke. Habe ich wohl überlesen :oops:
Gruß
Christoph

CS3+ 1.2.0 // CS2 4.1.0 // CS1 Aufgeladen 4.1.0 // MS2 // MS1 // 6021 // CAN-digital-Bahn by TM // Vitrinenbahner // Ausprobierer

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#91

Beitrag von Muenchner Kindl »

Hi,
Also kann ich die eine oder andere Schleife rausnehmen?
Richtig. Entweder die eine oder andere Schleife weniger durchlaufen lassen oder z.B. mit NOP - Befehlen Takte hinzufügen. Wenn man die Anzahl der Takte aller Befehle berücksichtigt (und den internen Oszillator kalibriert ;) ) kommt man sehr genau zu seinem Ergebnis.
Möglich ist aber auch die Annäherung a'la trial and error ;)

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#92

Beitrag von Muenchner Kindl »

Interupts allgemein

Damit das eigentliche Thema "Interupts" nicht so riesig, mächtig und unübersichtlich wird schiebe ich dieses allgemeinen Punkt vorher ein.

Wir haben bisher immer sequentiell programmiert, also eine feste Abfolge von Befehlen. Zwar mit Verzweigungen und Abhängigkeiten aber immer innerhalb einer festen Schiene.
In der Praxis gibt es aber sehr viele Dinge, die, völlig unabhängig vom gerade aktuellen Programmstatus, Einfluss nehmen müssen. Nehmen wir zum Beispiel den Nothalt einer Maschine:
Ein Prozessor steuert eine Maschine mit schweren rotierenden Wellen. Im Notfall reicht es vielleicht nicht, die Maschine stromlos zu machen, weil ja die Welle nachläuft. Hier ist es also erforderlich, das Programm zu unterbrechen und ein Unterprogramm aufzurufen, welches die Welle bremst und vielleicht andere Aktionen veranlasst.
Beim Stichwort "Unterprogramm" fällt uns sicher der Rcall-Befehl ein, nur der bringt uns nicht weiter, da er ja fest im Programm integriert ist.

Für solche und viele anderen Fälle gibt es Interupts, auf Deutsch "Unterbrechungen", welche ich zunächst in der Theorie beschreiben möchte.

Generell ist ein Interupt ein Ereignis, welches den Programmlauf anhält, zu einem festgelegten Unterprogramm springt und nach dessen Abarbeitung den Prozessor dazu veranlasst wieder an der Stelle weiterzuarbeiten, an der der Interupt ausgelöst wurde.
Technisch sieht das so aus, dass beim Auftreten des Interupt
- der aktuelle Befehl fertig abgearbeitet wird
- die folgende Adresse auf den Stack gelegt wird
- die dem jeweiligen Interupt zugewiesene Adresse der Interuptvektortabelle in den Programmzähler geladen wird
- der dort befindliche Befehl und alle darauffolgenden abgearbeitet wird
- nach einem entsprechenden Kommando die Rücksprungadresse aus dem Stack wieder in den PC geladen wird

Klingt alles grausam, ist aber nur halb so schlimm und mit dem Beispiel des uns längst bekannten Interupt "Reset" schnell erklärt.
Wird der Reset ausgelöst, wird die Adresse 0x0000 in den Programmzähler geladen. Ihr erinnert Euch sicher, was wir ziemlich am Anfang jeden Programmes stehen haben:

Code: Alles auswählen

.org 0x0000
        rjmp    Init                     ; Springe nach einem Reset zum Label "Init"
Damit beschreiben wir praktisch die Interuptvektortabelle und legen fest, dass nach dem Interupt "Reset" zum Label "Init" gesprungen werden soll.

Schauen wir mal in unsere Unterlagen auf Seite 46, da finden wir alle Interupts des ATM8 samt Adressen in der Interuptvektortabelle.
Angenommen unser Nothaltschalter von oben ist an INT0 angeschlossen, dann müssen wir dafür sorgen, dass in der Adresse 0x001 ein unbedingter Sprung zur jeweiligen Interuptserviceroutine steht.

Code: Alles auswählen

.org 0x0001
        rjmp    Nothalt                     ; Springe nach dem Auslösen von INT0 zum Label "Nothalt"
Bitte an dieser Stelle keinen Rcall oder einen bedingten Sprungbefehl setzen, warum dürft Ihr gerne als kleine Aufgabe herausfinden ;)

Die ISR sieht dann so aus:

Code: Alles auswählen

Nothalt:

Befehl 1
Befehl 2
Befehl 3
Befehl 4

reti
Im Prinzip ist das genauso aufgebaut wir bei einem normalen Unterprogramm, der Unterschied ist nur, dass eine Interuptserviceroutine mit reti abgeschlossen wird.

Hier aufpassen, es funktioniert nämlich auch mit ret... allerdings nur einmal!
Es ist nämlich so, dass man Interupts grundsätzlich erstmal erlauben muss (abgesehen von Reset). Wird eine ISR aufgerufen werden alle anderen Interupts gesperrt. Das Kommando reti holt nicht nur die Rücksprungadresse in den PC sondern gibt auch die Interupts wieder frei.

Im Gegensatz funktioniert reti auch in normalen Unterprogrammen. Werden diese aus einer ISR aufgerufen ist es aber schon vorbei, da Reti dann alle Interupts freigibt, während noch die ISR abgearbeitet wird.

Kleiner Nachtrag: Den Reset, den ich hier so selbstvertändlich als Beispiel verwendet habe ist zwar ein Interupt, unterscheidet sich aber deutlich von allen anderen. Beim Reset wird nichts auf den Stack gelegt (also keine Rücksprungadresse) und wenn mich nicht alles täuscht wird nichtmal der aktuelle Befehl abgearbeitet. Das Auslösen des Reset sperrt jedoch alle Interupts, weil das entsprechende Bit (dazu kommen wir am Wochenende) mit dem Reset gelöscht wird.

Am Wochenende werden wir uns einen Interupt genauer ansehen, soweit erstmal wieder von mir...

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#93

Beitrag von Muenchner Kindl »

Interrupts Timer


Ich habe die Tage das Thema "Interrupts allgemein" eingeschoben, damit wir hier wissen, um was es in etwa geht. Wir haben besprochen, was ein Interrupt ist, wie er funktioniert und dass er durch verschiedene Ereignisse ausgelöst werden kann. Ausserdem muss er generell erlaubt sein.

Heute schauen wir uns eine sehr praktische Angelegenheit der Atmels an, welche uns zwei Interrupts beschert (wovon wir aber nur einen nutzen werden), nämlich den/die Timer:

Stellen wir uns mal ein besonderes Register vor, welches, völlig unabhängig vom restlichen Programm, immer um 1 erhöht wird. Egal, was gerade passiert, in bestimmten Zeitabständen wird dieses Register inkrementiert und zwar zunächst ohne weiteren Code... einfach als Zuckerl von Atmel. Erreicht der Zähler seinen Höchststand wird eine bestimmte Aktion ausgeführt. Gerade für Lichteffekte aber auch für viele Anwendungen ein absoluter Traum, denn genau das gibt es und im ATM8 sogar dreimal! Nennen wir dieses Register mal "Timer" und schauen wir uns erstmal das Prinzip genauer an.

Wie schon geschrieben ist so ein Timer nichts anderes als ein Register, welches hardwaremäßig und fortlaufend hochgezählt wird. Ist der Zählerstand am Ende angelangt gibt es einen Überlauf (Overflow), welcher einen Interrupt auslöst. Wie wir ja bereits wissen wird das laufende Programm dann unterbrochen und die Interruptserviceroutine aufgerufen.

Unserem ATM8 hat man 3 Timer eingebaut. Dabei sind Timer0 und Timer2 jeweils 8-Bit-Register, Timer1 besitzt 16 Bit.

Wir nutzen für uns den recht einfachen Timer0 mit seinen 8 Bit und damit steigen wir ein. Das Register wird, wie schon beschrieben, unabhängig vom eigentlichen Programm, mit jedem Systemtakt um 1 erhöht. Bei unseren 8MHz und der Gegebenheit, dass es sich bei Timer0 um 8 Bit handelt wird der entsprechende Interrupt also alle 0,000032 Sekunden ausgelöst (1/8000000*255=0,000031875). Das ist schon ziemlich flott und für die meisten Anwendungen nicht sonderlich hilfreich. Aus diesem Grund gibt es für jeden Timer einen Vorteiler (Prescaler), den es zu definieren gilt.
Stelle ich diesen Prescaler z.B. auf 64, dann wird das Timerregister nicht mit jedem, sondern mit jedem 64sten Takt erhöht, die ISR also alle 1/8000000*64*255=0,00204 Sekunden aufgerufen.
Es gibt für Timer0 folgende Prescaler:
1
8
64
256
1024
Externer Takt (steigende oder fallende Flanke)

Damit wir das Ganze in der Praxis sehen basteln wir uns nochmal ein Blinklicht, welches im Sekundentakt ein und aus geht. Diesmal aber nicht mit doof verschachtelten Zeitschleifen sondern mit einem Timer.

Bevor es losgeht sollten wir uns überlegen, was wir brauchen:
Wir müssen einen Ausgang jede Sekunde umschalten (toggeln). Mit Timer0 und einen Prescaler 1024 bekommen wir alle 0,03264 Sekunden einen Aufruf der Interruptserviceroutine, in der wir den Ausgang toggeln können. Wir müssen dann dafür sorgen, dass der Ausgang nur nach jedem 30sten Unterprogrammaufruf (1/0,03264~30) getoggelt wird.

Dann noch zum Unterschied, wie unsere vorheriges Blinklicht funktioniert hat:

Code: Alles auswählen

Bisher:
- LED ein
- Zeit
- LED aus
- Zeit
-> Anfang

Code: Alles auswählen

Neu:
- Ist Timer 30mal gelaufen?
- Wenn ja, dann LED ein/aus
Dann sind wir also bei der Praxis und lernen erstmal zwei neue Befehle kennen:
SEI und CLI
Ob Interrups generell erlaubt sind oder nicht ist in Bit7 des Statustegisters (SREG) festgelegt, welches sich am Einfachsten durch sei (set interrupt enable) oder cli (clear interrupt) beeinflussen lässt.
Bevor wir jedoch die Interrupts global erlauben sollten wir unseren Timer nach unseren Bedürfnissen einstellen.
Dazu verweise ich mal wieder auf unser geliebtes Datenblatt und dabei auf Seite 69, wo es erstmal allgemein für Timer0 zur Sache geht. Wirklich interessant wird es aber bei Seite 71 (unten), denn damit unser Timer das macht was wir wollen müssen wir ihn mit Hilfe von Registern einstellen.

Den Prescaler stellen wir mit Hilfe der ersten drei Bits des Registers TCCR0 ein, die anderen Bits bleiben 0.
000 = kein Vorteiler (Timer ist angehalten)
001 = 1
010 = 8
011 = 64
100 = 256
101 = 1024
110 = ext. Takt mit fallender Flanke
111 = ext. Takt mit steigender Flanke

Wir müssen also dafür sorgen, dass der Wert 0x00000101 (=Vorteiler 1024) in das Register TCCR0 geschrieben wird.

Auf Seite 72 wird dann noch das Timer-Interrupt-Mask-Register TIMSK angesprochen. Auch wenn der Name noch so kompliziert ist, uns interessiert davon für uns nur das Bit 0, mit dem eingestellt wird, ob beim Auftreten des Overflows an Timer0 der entsprechende Interrupt ausgelöst wird.

Das Timer/Counter Register TCNT0 ist das eigentliche Timerregister, welches wir, zusammen mit TIFR ausser Acht lassen.

Kommen wir also zur Praxis und damit zu einem ersten Programm mit Timer:

Code: Alles auswählen

; Blinklicht mit Timer0

.include "m8def.inc"      ; Damit weis der Compiler, auf welchen Proz er compilieren muss

.def Time1 = r16

.org 0x0000
        rjmp    Init                  	; Springe nach einem Reset zum Label "Init"

.org 0x0009
		rjmp Timer_ISR					; Interruptvektor für Overflow Timer0



Init:									; Hier beginnt die Initialisierung


		; Setzen des Stackpointers
        ldi     r16, LOW(RAMEND)     	; unteres Byte der hächstmöglichen Adresse holen
        out     SPL, r16				; Unteres Byte des SP beschreiben
        ldi     r16, HIGH(RAMEND)		; oberes Byte der höchstmnöglichen Adresse holen
        out     SPH, r16				; oberes Byte des SP beschreiben

		; Initialisieren der Eingänge
		ldi r16, 0x00					; alle Bits in r16 auf 0 setzen
		out ddrd, r16					; Alle Pins von Port C sind Eingang

		ldi r16, 0xFF					; Bit 2 und Bit 3 setzen
		out portd, r16					; PC2 und PC3 bekommen PullUp-Widerstände

		; Initialisieren der Ausgänge
		ldi r16, 0xFF					; alle Bits in r16 auf 1 setzen
		out ddrb, r16					; Alle Pins von Port B sind Ausgang


		ldi r16, 0x00					; Alle Bits in r16 löschen
		out portb, r16					; Alle LEDs aus

		

		; Initialisieren von Timer0
		ldi r16, 0b00000101				; Prescaler 1024
		out tccr0, r16					; Prescaler in Timerregister schreiben

		ldi r16, 0b00000001				; TimerOverflow 0 einschalten
		out TIMSK, r16

		sei								; Interrupts erlauben

Hauptprogramm:
		rjmp Hauptprogramm				; Das Hauptprogramm ist nur eine Endlosschleife



Timer_ISR:								; Interruptserviceroutine für Timer0		
		sbis PortB, 0					; Ist LED1 ein?
		rjmp LEDein						; Wenn nein, dann zum Einschalten springen
		cbi PortB, 0					; LED1 ausschalten
		rjmp ISR_Ende					; Springe zum Ende

LEDein:
		sbi PortB, 0					; LED1 einschalten


ISR_Ende:
		reti							; Ende ISR
Wichtig ist der Aufbau der Interruptvektortabelle:

Code: Alles auswählen

.org 0x0009
      rjmp Timer_ISR               ; Interruptvektor für Overflow Timer0
Neu dazugekommen sind hier folgende Zeilen bei der Initialisierung:

Code: Alles auswählen

    ; Initialisieren von Timer0
      ldi r16, 0b00000101            ; Prescaler 1024
      out tccr0, r16               ; Prescaler in Timerregister schreiben

      ldi r16, 0b00000001            ; TimerOverflow 0 einschalten
      out TIMSK, r16
Wie Ihr seht macht es keinen Unterschied, ob man die Datenrichtungsregister eines IO-Ports oder ein Steuerregister eines Timers initialisiert. Es gibt noch viel mehr Steuerregister, das Prinzip und die Art und Weise des Beschreibens ist immer identisch.

Code: Alles auswählen

		sei								; Interrupts erlauben
Diese Zeile erklärt sich selbst

Code: Alles auswählen

Hauptprogramm:
      rjmp Hauptprogramm            ; Das Hauptprogramm ist nur eine Endlosschleife
Man beachte die Größe des Hauptprogramms ;)

Code: Alles auswählen

Timer_ISR:                        ; Interruptserviceroutine für Timer0      
      sbis PortB, 0               ; Ist LED1 ein?
      rjmp LEDein                  ; Wenn nein, dann zum Einschalten springen
      cbi PortB, 0               ; LED1 ausschalten
      rjmp ISR_Ende               ; Springe zum Ende

LEDein:
      sbi PortB, 0               ; LED1 einschalten
Auch die Interruptserviceroutine ist nicht wirklich gross und im Gegensatz zu dreifach verschachtelten Schleifen richtig übersichtlich ;)

Code: Alles auswählen

ISR_Ende:
      reti                     ; Ende ISR
Ganz wichtig: Interruptroutinen werden mit reti abgeschlossen!

Nun kopiert Euch dieses Programm in ein eigenes Projekt und probiert das mal aus. Wer einen Dauerblitzer haben will ist damit bereits am Ziel angelangt, wir wollen aber ~ 1 Sekunde haben und müssen nach vorheriger Berechnung dafür sorgen, dass der Ausgang nur alle 30 Aufrufe getoggelt wird.
Dazu sind in unserer ISR nur wenige Zeilen nötig:

Code: Alles auswählen

		inc Time1						; Time 1 hochzählen
		cpi Time1, 0x1E					; Wurde die ISR 30mal aufgerufen?
		brne ISR_Ende					; Wenn nein, dann springe zum Ende

		ldi Time1, 0x00					; Time1 zurücksetzen
Es wird eine Variable hochgezählt und solange die 30 (0x1E) nicht erreicht ist die ISR sofort wieder beendet. Wurde die 30 erreicht wird die Variable zurückgesetzt und danach der Ausgang getoggelt.

Das fertige Programm (etwa 1 Sekunde) schaut dann so aus:

Code: Alles auswählen

; Blinklicht mit Timer0

.include "m8def.inc"      ; Damit weis der Compiler, auf welchen Proz er compilieren muss

.def Time1 = r16

.org 0x0000
        rjmp    Init                  	; Springe nach einem Reset zum Label "Init"

.org 0x0009
		rjmp Timer_ISR					; Interruptvektor für Overflow Timer0



Init:									; Hier beginnt die Initialisierung


		; Setzen des Stackpointers
        ldi     r16, LOW(RAMEND)     	; unteres Byte der hächstmöglichen Adresse holen
        out     SPL, r16				; Unteres Byte des SP beschreiben
        ldi     r16, HIGH(RAMEND)		; oberes Byte der höchstmnöglichen Adresse holen
        out     SPH, r16				; oberes Byte des SP beschreiben

		; Initialisieren der Eingänge
		ldi r16, 0x00					; alle Bits in r16 auf 0 setzen
		out ddrd, r16					; Alle Pins von Port C sind Eingang

		ldi r16, 0xFF					; Bit 2 und Bit 3 setzen
		out portd, r16					; PC2 und PC3 bekommen PullUp-Widerstände

		; Initialisieren der Ausgänge
		ldi r16, 0xFF					; alle Bits in r16 auf 1 setzen
		out ddrb, r16					; Alle Pins von Port B sind Ausgang


		ldi r16, 0x00					; Alle Bits in r16 löschen
		out portb, r16					; Alle LEDs aus

		

		; Initialisieren von Timer0
		ldi r16, 0b00000101				; Prescaler 1024
		out tccr0, r16					; Prescaler in Timerregister schreiben

		ldi r16, 0b00000001				; TimerOverflow 0 einschalten
		out TIMSK, r16

		sei								; Interrupts erlauben

Hauptprogramm:
		rjmp Hauptprogramm				; Das Hauptprogramm ist nur eine Endlosschleife



Timer_ISR:
		inc Time1						; Time 1 hochzählen
		cpi Time1, 0x1E					; Wurde die ISR 30mal aufgerufen?
		brne ISR_Ende					; Wenn nein, dann springe zum Ende

		ldi Time1, 0x00					; Time1 zurücksetzen								
		sbis PortB, 0					; Ist LED1 ein?
		rjmp LEDein						; Wenn nein, dann zum Einschalten springen
		cbi PortB, 0					; LED1 ausschalten
		rjmp ISR_Ende					; Springe zum Ende

LEDein:
		sbi PortB, 0					; LED1 einschalten


ISR_Ende:
		reti							; Ende ISR
Bitte experimentiert ein wenig mit dem Timer, mit dem Prescaler und schaut mal, ob Euch noch was einfällt, was man damit machen kann.
Ihr könnt die ISR auch mal mit ret anstatt mit reti abschliessen. Schaut mal was passiert und überlegt Euch warum ;)

Ich werde jetzt wieder ein Wochenende auslassen, damit Ihr das doch recht komplexe Thema ein wenig verdauen könnt. Natürlich stehe ich für Fragen Rede und Antwort und wünsche wieder viel Spass.

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#94

Beitrag von Muenchner Kindl »

Retten von Registern

Hallo,

bevor ich Euch in Ruhe lasse und die Geschichte mit den Unterprogrammen und Interrupts zum Abschluss bringe muss ich noch etwas ergänzen, nämlich das Retten von Registern.

Was wir ganz am Anfang des Workshops schonmal angerissen haben wird spätestens bei der Abarbeitung von Interrupts akut.

Da wir ja nicht vorhersehen können, wann so ein Interrupt auftritt sollten generell alle Register, welche innerhalb der ISR verwendet werden, gerettet werden. Wichtig ist hier aber auch, das Statusregister SREG zu sichern, da es durchaus vorkommen kann, dass sich darin ein noch nicht verwertetes Ergebnis befindet, welches durch die ISR verändert werden könnte.

Das "Retten" von Registern erledigt der Befehl PUSH, gefolgt vom zu rettenden Register. Am Ende der ISR oder des Unterprogramms müssen die Register in umgekehrter Reihenfolge mit dem Befehl POP zurückgerettet werden:

Code: Alles auswählen

Beispiel-ISR:
  push Temp
  push Zeit
  push r18
  ...
  ...
  ...
  pop r18
  pop Zeit
  pop Temp
  reti
Was hier noch fehlt ist das Statusregister SREG (Flagregister), auf welches man leider nicht direkt zugreifen kann. Deswegen sollten unbedingt diese kommentierten Zeilen hinzugefügt werden:

Code: Alles auswählen

Beispiel-ISR:
  push Temp
  push Zeit
  push r18
  in Temp, sreg   ;Statusregister nach Temp laden
  push Temp       ;Variable mit Inhalt des sreg auf Stapel legen
  ...
  ...
  ...
  pop Temp        ;Inhalt von sreg vom Stapel holen
  out sreg, Temp ;SREG wiederherstellen
  pop r18
  pop Zeit
  pop Temp
  reti
Je nachdem, was in der ISR passiert müssen die Register womöglich nicht gerettet werden, das Statusregister sollte aber auf jeden Fall auf diese Art und Weise gesichert werden!

Und jetzt habt Ihr wieder Ruhe vor mir ;)

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#95

Beitrag von Muenchner Kindl »

Hallo,

bevor es kommendes Wochenende mit dem nächsten grossen Thema weitergeht eine kleine Bitte.

Ich habe keinerlei Anhaltspunkt darüber, wie der Workshop von den Teilnehmern aufgenommen wird. Da es bis auf wenige Ausnahmen keinerlei Fragen oder Anregungen ect. gibt kann ich entweder davon ausgehen, dass alles für alle verständlich ist (das wäre mein Wunsch) oder dass das keinen mehr interessiert, weil vielleicht doch zu schnell oder zu unverständlich.

Nun, auch wenn ich öfter mal darauf hingewiesen habe... wenn etwas zu schnell, zu unverständlich oder sonstwie unbrauchbar ist, bitte zieht die Notbremse. Es gibt einige Teilnehmer, die haben über 70€ für das Equipment ausgegeben, es wäre schade wenn das für die Katz wäre. Es ist auch keine Schande, wenn etwas nicht verstanden wird. Das Problem ist nur, dass wir sämtliche Themen nur oberflächlich bestreifen können. Zwar so, dass man damit arbeiten kann aber z.B. könnte man alleine für das Thema Interrupts einen eigenen Workshop anbieten. Hier, wie bei allen Themen, möchte ich die Grundlagen vermitteln, die dann jeder nach Bedarf vertiefen kann. Wenn es aber hier schon Schwierigkeiten gibt braucht man mit dem Vertiefen gar nicht anfangen.

Das nächste Thema handelt vom Beschreiben und Auslesen des SDRAM. Die Interrupts werden uns, wie alles andere auch, weiterverfolgen. Womöglich werde ich das SDRAM-Thema auch aufteilen (überlege ich mir noch). Wir werden sowas wie eine kleine Datenbank (im weitesten Sinne) aufbauen und wir werden mindestens 4 neue Befehle kennenlernen. Wenn danach Fragen zum Thema Unterprogramme oder Timer kommen ist das wenig hilfreich.

Also, wenn es hakt -> BREMSE!

Ansonsten weiterhin viel Spass :-)
Benutzeravatar

kaeselok
ICE-Sprinter
Beiträge: 6785
Registriert: Mo 30. Apr 2007, 13:15
Nenngröße: 1
Stromart: digital
Steuerung: ECoS II / TAMS
Gleise: Hübner nach NEM
Wohnort: Brühl

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#96

Beitrag von kaeselok »

Ich lese weiterhin fleissig mit! Noch (!) klappt es auch mit dem Verstehen! (was aber auch an den exzellenten Erklärungen des Autors liegt!)

Jetzt so kurz vor Weihnachten geht wohl bei vielen Kollegen die Post ab, da bleiben solche Projekte wohl eher auf der Strecke? (ist zumindest bei mir so ... :oops: )


Viele Grüße,

Kalle

Ferenc
Metropolitan (MET)
Beiträge: 3255
Registriert: Mo 26. Sep 2005, 23:35
Nenngröße: H0
Stromart: AC
Steuerung: Ecos 1 + Tams MC + TC Gold
Gleise: Märklin M-K-C Gleis
Wohnort: HDH
Alter: 56

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#97

Beitrag von Ferenc »

Hallo,
was soll mit dem LCD Display angezeigt werden. Oder nenn uns mal ein Beispiel was du hier machen würdest.

Ferenc
Immer eine Handbreit Schotter unter der Schwelle ;-)

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#98

Beitrag von Muenchner Kindl »

Ferenc hat geschrieben:Hallo,
was soll mit dem LCD Display angezeigt werden. Oder nenn uns mal ein Beispiel was du hier machen würdest.

Ferenc
Hi,

ich habe den Hinweis zwar beteits wieder entfernt, weil ich mir noch über ein paar Details im Klaren sein will aber um Deine Frage zu beantworten... es ginge rein darum, wie man ein solches Display ansteuert und wie man z.B. einen Text darauf bringt.
Für den praktischen Einsatz auf der Modellbahn fällt mir allerdings kein Einsatzgebiet ein und deshalb glaube ich nicht, dass das ein sinnvolles Thema ist.
Benutzeravatar

DeMorpheus
Metropolitan (MET)
Beiträge: 3632
Registriert: Mi 22. Dez 2010, 13:08
Nenngröße: H0
Steuerung: MS2 Gleisbox + BPi
Gleise: C-Gleis: ML+2L-fähig
Wohnort: Aachen
Alter: 30
Kontaktdaten:

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#99

Beitrag von DeMorpheus »

Hallo Thomas,

ich bin bisher ganz gut mitgekommen und glaube, alles verstanden zu haben.

Inhaltlich/verstandnismäßig finde ich den Workshop nicht zu schnell, was sicherlich an deinen guten Erklärungen liegt.
Das „Problem“ ist nur, sich mal ein, zwei Stunden Zeit zu nehmen und das Thema zu verarbeiten. Aber wenn ich sehe,
wie viel Mühe und ja wohl auch Zeit du in deine Vorbereitungen investierst, erscheint mir meine Zeitknappheit nur als
faule Ausrede ... also werde ich heute Abend das aktuelle Thema endlich bearbeiten ;)
Viele Grüße,
Moritz
'Nitwit! Blubber! Oddment! Tweak!'

Threadersteller
Muenchner Kindl
Moderator
Beiträge: 10214
Registriert: Di 26. Apr 2005, 21:26

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#100

Beitrag von Muenchner Kindl »

Hallo,

vielen Dank, auch für die Antworten, die mich per PN erreicht haben!

Es freut mich sehr, dass mein Geschriebenes bei Euch ankommt und die Arbeit nicht umsonst ist. Oft ist es ja so, dass man mit einem Vortragsstil nicht zurecht kommt, sich aber nicht getraut, das zu sagen.

Ich werde die Tage das SDRAM-Thema anfangen, so dass wir über Weihnachten Ruhe haben ;) . Ob wir zwischen Weihnachten und Neujahr noch was machen überlasse ich dann Euch. Das SD-Ram-Thema werde ich in einem abarbeiten, evtl. aber in zwei Beiträge aufteilen.

Danke nochmal Euch allen :D
Antworten

Zurück zu „Workshops“