Workshop: Programmierung in Assembler (Atmel ATM8)

Workshops von Usern dieses Forums angeboten.
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)

#101

Beitrag von kaeselok »

Wir haben zu danken für die Arbeit die Du Dir machst!!! :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)

#102

Beitrag von Muenchner Kindl »

SRAM adressieren, beschreiben, auslesen Teil 1 (reservierte Bytes)

Guten Morgen,

Wie schon angekündigt befassen wir uns vor Weihnachten noch mit dem Thema "SRAM" und auch wenn der Beitrag vermutlich recht gross werden wird, das Thema ist an und für sich einfach.

Bevor es losgeht ein Hinweis: Ich habe gestern ein paar Seiten gefunden, in denen der Zugriff auf den Flash des Controllers sehr wohl möglich ist. Da ich dafür allerdings keine Verwendungsmöglichkeit sehe lasse ich das Thema aus.

Nun, wie wir ja wissen beinhaltet unser Mikrocontroller neben dem Prozessor auch einen Flash, einen EEProm, sowie ein schnelles SRAM. Letzteres ist im Prinzip mit dem Arbeitsspeicher eines PC vergleichbar, wenn auch mit einem Unterschied:
Ein x86er besitzt intern eigene Arbeitsregister, die mit dem Arbeitsspeicher und/oder dem Cache nichts zu tun haben. Bei unseren Atmels sind die Arbeitsregister ein Bestandteil des SRAM und damit haben wir die erste von drei in diesem Workshop behandelten Möglichkeiten, das SRAM zu benutzen.
Nun folgt der oblikatorische und mittlerweile hoffentlich reflexartige ;) Blick ins Datenblatt, wo uns auf Seite 18 der Aufbau des SRAM offenbart wird. Hier seht Ihr in der kleinen Grafik nicht nur die ganzen Register und deren fest zugewiesenen Adressen im SRAM, auch die ganzen anderen bisher behandelten Steuerregister sind im SRAM zu finden.
Mit dem Befehl ldi r29, 0x00 machen wir also nichts anderes, als in die Adresse 0x001D des SRAM den Wert 0x00 hineinzuschreiben. Diese Art und Weise, das SRAM zu beeinflussen ist eigentlich längst in Fleisch und Blut übergegangen und muss sicher nicht mehr ausgeführt werden. Eigentlich grabscht so ziemlich jeder Befehl im SRAM herum, immerhin beeinflusst so ziemlich jeder Befehl ein Register...

Nun ist ja die Anzahl der direkt ansprechbaren Register ziemlich beschränkt, zumal einige Register, auf die wir später noch zu sprechen kommen, noch ein paar Sonderaufgaben haben und nicht immer frei zur Verfügung stehen. Es gibt aber durchaus Fälle, wo ich einfach mehr Register, eben Ablageplätze, brauche. Sei es, um viele Daten abzulegen oder auch im Konstanten in übersichtlicher Form zur Verfügung zu haben.

Greifen wir nochmal die Ampel an, diesmal allerdings in vereinfachter Form. Lasst uns einfach mal nur einen Signalschirm einer Ampelanlage betrachten. Wir finden hier die Zustände Rot, Rot/Gelb, Grün und Gelb. Programmtechnisch würden wir das derzeit so abbilden:

- Bitmuster Rot ausgeben
- lange Zeit warten
- Bitmuster Rot/Gelb ausgeben
- kurze Zeit warten
- Bitmuster Grün ausgeben
- lange Zeit warten
- Bitmuster Gelb ausgeben
- kurze Zeit warten
- springe zum Anfang

Das jeweilige Bitmuster würden wir jedesmal in der entsprechenden Zeile beschreiben, um es dann auszugeben, zumindest haben wir das bisher so gemacht.

Wenn wir uns jedoch ein komplexeres Programm vorstellen, wo eine gewisse Anzahl vordefinierter Bitmuster oder auch Konstanten, an vielen Stellen ausgegeben oder verarbeitet werden sollen, dann wird das Ganze nicht nur unübersichtlich sondern auch Fehlerbehaftet. Will man ein definiertes Bitmuster ändern, so muss man das an allen Stellen machen, wo dieses Bitmuster zur Verwendung kommt, viel Freude hat man damit nicht.

Nun könnten wir uns vier Register reservieren, ihnen tolle Namen geben und während der Initialisierung mit den Bitmustern beschreiben.

Code: Alles auswählen

r16= rot
r17= rotgelb
...

ldi rot, 0b00000001
ldi rotgelb, 0b00000011
ldi gruen, 0x0b00000100
...
und so weiter
Soll das Bitmuster ausgegeben werden kommt einfach nur

Code: Alles auswählen

...
out portb, rot  ;Rot ausgeben
rcall Zeit_lang   ;lange Zeitschleife
...
Bei unserer einfachen Ampel geht das noch, komplexere Aufgaben mit vielen Konstanten und Bitmustern lassen sich damit nicht lösen.

Unser ATM Studio bietet uns dafür die Möglichkeit, Speicherstellen im SRAM für unsere Zwecke zu reservieren und zu verwenden. Dazu wird am Ende des Programms ein Datensegment angelegt, in dem die zu definierenden Bytes benannt werden. Klingt kompliziert, ist aber einfacher als ein Butterbrot zu schmieren...:

Das Datensegment leiten wir mit ".DSEG" am Ende des Programmes ein. Anschliessend werden unsere Bytes reserviert, für unsere Ampel sieht das so aus:

Code: Alles auswählen

...
.DSEG                     ;Beginn des Datensegments
rot:          .BYTE 1   ;Variable "rot" mit 1 Byte festlegen
rotgruen:  .BYTE 1  ;Variable "Rotgruen" mit 1 Byte festlegen
gruen:      .BYTE 1  ;Variable "Gruen" mit 1 Byte festlegen
gelb:         .BYTE 1  ;Variable "Gelb" mit 1 Byte festlegen
Wir haben damit also die Variablen definiert und können darauf jederzeit, leider nicht direkt, zugreifen, wir benötigen immer ein Arbeitsregister dazwischen.

Wollen wir also die Variable "rot" mit dem Wert 0x01 (Bit0 soll die rote Lampe sein und die ist damit gesetzt gesetzt) beschreiben müssen wir dies zuerst in ein Arbeitsregister packen:

Code: Alles auswählen

ldi r16, 0x01   ;Bitmuster für "rot" in r16 schreiben
Anschliessend, und jetzt kommt der erste neue Befehl des Themas, speichern wir den Inhalt von R16 in das durch "rot" benanntes Byte im SRAM:

Code: Alles auswählen

sts rot, r16
Ich muss sicher nicht erwähnen, dass es einen Variablennamen im ganzen Programm nur einmal geben darf. In unserem Fall gibt es also kein Register und kein Label mehr mit dem Namen "rot"!

Die Initialisierung unseres Ampelprogrammes könnte also um folgende Zeilen ergänzt werden (Bit0 ist rote, Bit1 gelbe und Bit2 die grüne Lampe)

Code: Alles auswählen

ldi r16, 0x01      ;Bit0 für rote Lampe setzen
sts rot, r16       ;Bitmuster nach "rot" speichern

ldi r16, 0x03     ;Bit0 und Bit1 für rote und gelbe Lampe setzen
sts rotgelb, r16 ;Bitmuster nach "rotgelb" speichern

ldi r16, 0x04     ;Bit2 für grüne Lampe setzen
sts gruen, r16   ;Bitmuster nach "gruen" speichern

ldi r16, 0x02      ;Bit1 für gelbe Lampe setzen
sts gelb, r16     ;Bitmuster nach "gelb" speichern
(im DSEG zum Ende des Programms sind die Variablen selbstverständlich, wie oben beschrieben, zu definieren.

Kommen wir nun zu dem Punkt, wo wir die abgelegten Bitmuster ausgeben, bzw. verarbeiten wollen:
Hierzu, und das ist der Pferdefuss, müssen wir uns wieder mit einem Arbeitsregister dazwischen behelfen, denn weder das definierte Byte im Ram, noch der IO-Port lassen sich direkt ansprechen. Die Stelle, an der das Lichtzeichen rot/gelb ausgegeben werden soll sieht damit so aus:

Code: Alles auswählen

lds r16, rotgelb    ;Bitmuster für rotgelb nach r16 laden
out Portb, r16      ;r16 an PortB ausgeben
Das Gegenstück zum sts heisst also lds und damit wird lediglich der Inhalt des benannten Byte in das jeweilige Register geholt.

Der Vorteil, die Bitmuster und Konstanten derart zu verwalten, liegt auf der Hand. Soll die grüne Lampe an Bit3 angeschlossen werden, dann ist dies nur in einer einzigen Zeile, nämlich in der Initialisierung, anzupassen, egal wie oft das Bitmuster "gruen" im Programm verwendet wird.

Selbstverständlich können derart definierte Bytes im SRAM auch für anderes, z.B. für Ergebnisse zur Laufzeit verwendet werden, sinnvoll dann wenn die Register nicht ausreichen. Allerdings sollte man bei zeitkritischen Variablen doch eher zusehen, dass man dafür Register verwendet, das erspart den einen oder anderen Systemtakt.

Hier der Code für unsere Verkehrsampel, allerdings vereinfacht und auf einen Schirm reduziert. Zunächst mag der Code umfangreicher erscheinen, bei komplexeren Anwendungen erleichtert die Verwendung von reservierten Bytes ungemein.

Code: Alles auswählen

; automatische, vereinfachte 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

;Ampelfarben definieren

	ldi r16, 0x01				; Bitmuster für "Rot"
	sts Rot, r16				; Bitmuster in SRAM ablegen

	ldi r16, 0x03				; Bitmuster für "RotGelb"
	sts RotGelb, r16			; Bitmuster in SRAM ablegen

	ldi r16, 0x04				; Bitmuster für "Gruen"
	sts Gruen, r16				; Bitmuster in SRAM ablegen

	ldi r16, 0x02				; Bitmuster für "Gelb"
	sts Gelb, r16				; Bitmuster in SRAM ablegen




Hauptprogramm:
	lds Temp, Rot				; Bitmuster für Rot laden
	out PortB, Temp				; Ausgabe Bitmuster
	rcall Lang					; Lange Zeitschleife

	lds Temp, RotGelb			; Bitmuster für Rot/Gelb laden
	out PortB, Temp				; Ausgabe Bitmuster
	rcall Kurz					; Kurze Zeitschleife

	lds Temp, Gruen				; Bitmuster für Gruen laden
	out PortB, Temp				; Ausgabe Bitmuster
	rcall Lang					; Lange Zeitschleife

	lds Temp, Gelb		; Bitmuster für Gelb laden
	out PortB, Temp				; Ausgabe Bitmuster
	rcall Kurz					; Kurze Zeitschleife

	rjmp Hauptprogramm			; Zurück zum Anfang



;Unterprogramme:
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


; Datensegment
.DSEG

Rot: 		.BYTE 1
RotGelb:	.BYTE 1
Gruen:		.BYTE 1
Gelb:		.BYTE 1
Viel gibt es nicht zu experimentieren. Vielleicht mal die Lampen einem anderen Ausgang zuordnen oder die Signalbilder andersweitig manipulieren.

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#103

Beitrag von Muenchner Kindl »

SRAM adressieren, beschreiben, auslesen Teil 2 (Pointer)

Der Übersicht halber habe ich die SRAM-Geschichte in zwei Teile aufgeteilt, was jedoch nichts über den Schwierigkeitsgrad aussagt.

In Teil 1 haben wir das SRAM über Variablen angesprochen, um Register zu sparen oder um schnell an mehrere Konstanten zu kommen.
In diesem Teil werden wir das SRAM in etwa so ansprechen, wie wir eine Datenbank ansprechen würden, in höheren Programmiersprachen spricht man bei durchaus vergleichbaren Funktionen öfter auch von Arrays.

Nicht selten kommt es vor, dass ein Programm sehr viele Ergebnisse speichern muss, und zwar abhängig von wiederum einem anderen Ergebnis.
Ich nehme mir mal als Beispiel den RC-Link von Tams, welcher für jeden seiner maximal 24 Detektoren die ermittelte Lokadresse ablegen muss, um sie später an den PC weiterzureichen, bleibe allerdings bei der einfachen Betrachtung (der RC-Link verwaltet tatsächlich pro Detektor 6 Bytes, eine Lokadresse beansprucht dabei 2 Bytes)
Nun könnte ich für jeden Detektor ein Byte reservieren, was jedoch bei der Auswertung sehr umständlich wäre. Wollte ich damit wissen, welche Adresse in Detektor 2 steht sähe das so aus:

Code: Alles auswählen

;in Register r16 steht die auszulesende Detektoradresse, die darin enthaltene Lokadresse soll nach r17 geholt werden

Abfrage1:
cpi r16, 0x01          ;soll Detektor1 ausgelesen werden?
brne Abfrage2         ;wenn nein, weiter mit Abfrage 2
lds r17, Detektor1  ;Detektor1 einlesen
rjmp Abfrage_ende ;keine weitere Abfrage

Abfrage2:
cpi r16, 0x02           ;soll Detektor2 ausgelesen werden?
brne Abfrage2          ;wenn nein, weiter mit Abfrage 3
lds r17, Detektor2    ;Detektor2 einlesen
rjmp Abfrage_ende  ;keine weitere Abfrage

Abfrage3:
...

Abfrage24:
...
Wie Ihr sicher seht macht das bestimmt viel Freude, 24 Abfragen zu definieren... könnte der RC-Link 255 Detektoren wäre das eine Arbeit für Schwerverbrecher.

Natürlich geht das viel viel eleganter, einfacher und kürzer, allerdings benötigen wir dazu ein paar Register:
Die Register r26 und r27, r28 und r29 sowie r30 und r31 lassen sich nämlich zu Paaren zusammenfassen, welche eine besondere Aufgabe bekommen können. So handelt es sich bei dem Registerpaar
- r26 / r27 um den X-Pointer
- r28 / r29 um den Y-Pointer
- r30 / r31 um den Z-Pointer

Mit Hilfe dieser Pointer und den entsprechenden Befehlen lassen sich Speicheradressen direkt adressieren und auslesen. Das Register mit der kleineren Zahl enthält dabei das Lower Byte, das mit der größeren Zahl das Higher Byte.
Um die Speicheradresse 0x0200 mit dem Z-Pointer auszulesen müssen die beiden Register wie folgt befüllt werden:

Code: Alles auswählen

ldi r30, 0x00  ;Lower Byte in Z-Pointer schreiben
ldi r31, 0x02  ;Higher Byte in Z-Pointer schreiben
Ist der Pointer, in diesem Fall der Z-Pointer geladen, dann kann ich mit dem Befehl ld das damit adressierte Byte auslesen.

Code: Alles auswählen

ld r17, Z
schreibt also den Inhalt des Bytes, welches im Z-Pointer adressiert wurde, in das Register r17.

Klingt vielleicht kompliziert, ist aber eigentlich ganz einfach. Kommen wir wieder auf unseren sehr vereinfachten RC-Link mit seinen 24 Detektoren zurück und nehmen wir mal an, die erkannte Lokadresse von
Detektor 1 steht in 0x0201
Detektor 2 steht in 0x0202
Detektor 3 steht in 0x0203
...
Detektor 24 steht in 0x0218

Nun können wir das Higher Byte des Z-Pointers gleich mal festlegen mit 0x02, denn das ist ja immer gleich, in das Lower Byte kommt lediglich die auszulesende Detektornummer.

Wir gehen mal davon aus, in R31 wurde bei der Initialisierung schon der Wert 0x02 geschrieben und wir betrachten nun den Teil, der die einem Detektor zugeordnete Lokadresse aus dem SRAM nach r17 holt. Die Detektornummer steht dabei wieder in r16:

Code: Alles auswählen

mov r30, r16  ;Detektornummer in Z-Pointer kopieren
ld r17, Z       ;Inhalt von durch Z-Pointer adressierte Speicherstelle nach r17 laden
Das war es bereits... im Gegensatz zu den 24 Abfragen sicher eine Erleichterung, oder ;) ?

Der Hauptvorteil des Zugriffs via Pointer ist, dass ich mit diese rechnerisch beeinflussen kann. Angenommen ich will die Inhalte aller 24 Detektoren nacheinander auslesen und ausgeben kann ich das mit einer einfachen Zählschleife machen:

Code: Alles auswählen

ldi r30, 0x00   ;Startwert in Z-Pointer LowerByte schreiben
Zaehlschleife
inc r30     ;Z-Pointer inkrementieren
ld r17, Z    ;Inhalt von SDRAM nach r17
out Portb, r17   ;Ausgabe an Port
cpi r30, 0x18   ;Sind die 24 Speicherstellen durchgezählt?
brne Zaehlschleife   ;wenn nein, dann weiterzählen
Hätten wir 255 Detektoren mit 255 Speicherbytes, dann würden wir die Zählschleife halt bis 255 zählen lassen...

Das Beschreiben über Pointer funktioniert übrigens äquivalent mit dem Befehl SD. Nach dem Laden, z.B. des Y-Pointers (r28 und r29), kommt

Code: Alles auswählen

sd y, r17   ;Speichern von r17 an das im Y-Pointer adressiere Byte im SDRAM
Können wir das für unsere Ampel gebrauchen? Ja, wenn auch nicht sonderlich sinnvoll. Dazu legen wir folgendes Fest:

0x0201 = 0b00000001 = Bitmuster rot
0x0202 = 0b00000011 = Bitmuster rot/gelb
0x0203 = 0b00000100 = Bitmuster grün
0x0204 = 0b00000010 = Bitmuster gelb

Nun laden wir den Z-Pointer erstmal vor und basteln uns eine Schleife:

Code: Alles auswählen

Schleife:

ldi r30, 0x01    ;0x201 ist Bitmuster rot
rcall Ausgabe   ;Ausgabe
rcall Zeit_lang  ; lange Zeitschleife

inc r30            ;0x202 ist Bitmuster rot/gelb
rcall Ausgabe   ;Ausgabe
rcall Zeit_kurz  ;kurze Zeitschleife

inc r30            ;0x203 ist Bitmuster grün
rcall Ausgabe   ;Ausgabe
rcall Zeit_lang   ;lange Zeitschleife

inc r30           ;0x204 ist Bitmuster gelb
rcall Ausgabe  ;Ausgabe
rcall Zeit_kurz ;kurze Zeitschleife

rjmp Schleife   ;Zurück zum Anfang

Ausgabe:       ;Unterprogramm zur Ausgabe
ld r16, Z        ;Durch Z-Register adressiertes Byte nach r16 laden
out PortB, r16 ;Bitmuster ausgeben
ret                ;Ende Unterprogramm
Wie Ihr seht kann ich die auszulesende (oder auch die zu beschreibende!) Adresse jederzeit mit Rechenoperationen beeinflussen.
Allerdings ist darauf zu achten, dass man mit dem Adressieren den Stack berücksichtigt, dieser "wächst" nämlich von oben nach unten und würde, entsprechend adressiert, ebenso verändert oder ausgelesen werden.

So, jetzt gibt es erstmal was zum Ausprobieren:

Code: Alles auswählen

; Lichteffekte

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

.def Temp = r16
.def Time = r17

.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

		ldi Time, 0xFF					; Zaehler zurücksetzen



		; Initialisieren der Lichteffekte
		ldi r31, 0x02					; HigherByte in Z-Register laden
		ldi r30, 0x00					; LowerByte in Z-Register laden


		;---Hier werden die Bitmuster für die Lichteffekte angelegt---
		ldi temp, 0b00011000
		st z, temp

		inc r30
		ldi temp, 0b00100100
		st z, temp

		inc r30
		ldi temp, 0b01000010
		st z, temp

		inc r30
		ldi temp, 0b10000001
		st z, temp

		inc r30
		ldi temp, 0b11000011
		st z, temp

		inc r30
		ldi temp, 0b01100110
		st z, temp

		inc r30
		ldi temp, 0b00011000
		st z, temp

		inc r30
		ldi temp, 0b00111100
		st z, temp

		inc r30
		ldi temp, 0b01111110
		st z, temp
		;---Ende der Definition der Bitmuster---

		ldi r30, 0x00  					; Z-Pointer zurücksetzen


		sei								; Interrupts erlauben

Hauptprogramm:
		rjmp Hauptprogramm				; Das Hauptprogramm ist nur eine Endlosschleife



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

		ldi Time, 0x00					; Time1 zurücksetzen								
		
Effekt:
		ld Temp, z						; Bitmuster aus SRAM laden
		out portb, temp					; Bitmuster ausgeben

		inc r30							; Z-Pointer inkrementieren
		cpi r30, 0x09					; Hat r30 den Wert 9 erreicht?
		brne ISR_Ende					; Wenn nein, dann ISR beenden

		ldi r30, 0x00					; Z-Pointer zurücksetzen


ISR_Ende:
		reti							; Ende ISR
Der Experte erkennt sicher sofort, dass ich hier den Code vom Timer-Experiment weitestgehend übernommen habe ;)
Hinzugekommen sind ein paar Zeilen bei der Initialisierung und ein paar Veränderungen in der Interrupt-Serviceroutine.

Was soll das darstellen?

Im Prinzip habt Ihr damit einen einfachen Lichteffektcomputer. Im Initialisierungsteil des Programms habt Ihr die Möglichkeit, 9 verschiedene Bitmuster einzutragen, die dann mit kurzem Abstand an die LEDs ausgegeben werden. Neben den Bitmustern könnt Ihr auch am Abstand schrauben, und wer mir sagen kann wo (es gibt zwei Stellen... allerdings ist nur eine Stelle sinnvoll) bekommt einen Punkt (und wer die meisten Punkte hat bekommt einen Punkt)

Bevor ich das Thema abschliesse weise ich nochmal auf das Datenblatt und dessen Verwendung hin. Schaut Euch bitte auf S.18 die Data Memory Map an. Hier seht Ihr, welche Adressbereiche Ihr verwenden könnt, beim ATM8 wäre das der Bereich 0060 - 045F. Allerdings, und das steht natürlich nicht im Datenblatt, haltet Euch am Ende des Bereichs genug für den Stack frei. Vielleicht den Bereich von 0400 - 045F nicht benutzen.

Das war evtl. das letzte Thema dieses Jahres, 2012 geht es weiter... allerdings stehe ich gerne für Fragen zur Verfügung.
Benutzeravatar

Rainer Müller
InterRegio (IR)
Beiträge: 191
Registriert: Do 29. Jun 2006, 10:30
Nenngröße: H0
Wohnort: Korntal
Kontaktdaten:

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#104

Beitrag von Rainer Müller »

Hallo Thomas,

erlaubst Du einem außenstehenden Mitleser eine Korrektur?
Muenchner Kindl hat geschrieben:Nun, wie wir ja wissen beinhaltet unser Mikrocontroller neben dem Prozessor auch einen Flash, einen EEProm, sowie ein schnelles SDRAM.
Da ist ein "D" zuviel, der Atmel hat SRAM (statisches RAM, siehe http://de.wikipedia.org/wiki/Static_Ran ... ess_Memory ) und kein SDRAM (synchrones dynamisches RAM, siehe http://de.wikipedia.org/wiki/SDRAM ).

Gruß
Rainer

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#105

Beitrag von Muenchner Kindl »

Rainer Müller hat geschrieben:Hallo Thomas,

erlaubst Du einem außenstehenden Mitleser eine Korrektur?

...

Gruß
Rainer
Hallo Rainer,

selbstverständlich und dafür bin ich ausserordentlich dankbar. Irgendwie hat sich das "D" in die Gedanken geschmuggelt und ging nicht mehr raus. Naja, da habe ich ja jetzt was zu tun :mad:

Danke! :D

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#106

Beitrag von Muenchner Kindl »

Guten Morgen,

bitte nicht erschrecken, ich habe nicht vor, Euch am zweiten Weihnachtsfeiertag ein neues Thema zu bescheren :mrgreen:

Ich wollte nur Bescheid geben, dass ich kommendes Wochenende auch nicht dazu kommen werde, Euch zu quälen :twisted: , also gibt es quasi Ferien bis zum ersten Januar-Wochenende.

Einen schönen Feiertag noch und einen guten Rutsch und falls einer so verrückt ist... ich stehe natürlich zur Verfügung ;)

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#107

Beitrag von Muenchner Kindl »

EEPROM beschreiben und auslesen

Neues Jahr, neues Thema und als guten Vorsatz noch mehr Befehle und Register :twisted: ...

Nachdem wir uns mit an dem SRAM und seine Verwendung über zwei Themenblöcke erfreut haben machen wir mit einem anderen Speicher weiter, dem EEPROM.
Hierbei handelt es sich grundsätzlich um einen elektronisch löschbaren, programmierbaren Festwertspeicher, electrically erasable programmable read-only memory. Im Prinzip ist das sowas wie die Festplatte unseres Prozessors, ein nicht flüchtiger, im Vergleich zum SDRAM langsamer Speicher.

Während wir im SRAM hauptsächlich Ergebnisse und Zustände ablegen, die sich schnell verändern oder schnell zur Verfügung stehen müssen können wir das EEPROM zum Ablegen von Daten nutzen, die beim nächsten Einschalten wieder benötigen werden. Wenn wir die digitale Modellbahn als Beispiel nehmen, hier würden wir die aktuelle Fahrstufe sicher im SRAM ablegen, während wir die Adresse in den EEPROM legen.
Das EEPROM bietet zudem die Möglichkeit, bereits zur Entwicklungszeit dort Daten abzulegen, z.B. die Seriennummer, Artikelnummer... oder den ermittelten OSCCAL-Wert. Letzteres lässt sich, zumindest im Studio4, innerhalb der Maske erledigen:
Bild
Hier einfach die Option "EEPROM" setzen, die gewünschte Adresse eingeben und den ermittelten Wert ins EEPROM schreiben.

Damit sind wir bei der "gewünschen Adresse"... wie sieht es denn damit aus? Dreimal dürft Ihr raten, wo ich Euch wieder hinschicke... auf Seite 19 findet Ihr eine, leider recht mickrige Information über den Adressbereich des EEPROM. Es ist von 512 Byte die Rede, und da wir hier auch bei 0x0000 anfangen werden geht der mögliche Adressbereich von 0x0000 bis 0x01FF.

Für den Fall, dass zur Entwicklungszeit bereits Daten im EEPROM abgelegt werden (Seriennummern, OSCCAL ect.) bietet es sich an, die auserwählten Adressen am Anfang des Programms als Kommentare zu dokumentieren. Z.B.:

Code: Alles auswählen

; Blitzlichteffekte mit kallibriertem Oszillator
; OSCCAL-Wert befindet sich im EEPROM unter 0x00FF
...
Bevor wir uns mit dem Beschreiben und Auslesen des EEPROM beschäftigen noch ein Hinweis: Wird ein Hexfile, also ein compiliertes Programm in den Flash geschrieben, wird standardmäßig der EEPROM gelöscht! Wenn das nicht passieren soll ist das entsprechende Fusebit zu setzen:
Bild
Unter der Karteikarte "Fuses" ist der Haken bei EESAVE zu setzen, dann wird das EEPROM beim Aufspielen des Programms verschont. Für unsere Arbeit hier spielt das keine Rolle, da wir das EEPROM nur zur Laufzeit beschreiben werden, und da auch nicht mit wirklich wichtigen Informationen.

Bitte spielt mir an den Fuses nicht unnötig herum. Es ist durchaus möglich, Eueren Prozessor damit anzuschiessen und für den Programmer unerreichbar zu machen. Ist zwar kein Beinbruch, Reichelt hat genug davon... ärgerlich aber trotzdem.

Und jetzt ist es so weit, dass es so weit ist... wir gehen ans Eingemachte. Neue Befehle gibt es diesmal keine aber massenweise neue Register und alle findet Ihr natürlich samt Beschreibung im Datenblatt Seite 20.
Wir haben hier folgende Register, um die wir uns kümmern müssen:

EEDR (EEProm Data Register): Hier kommt das Byte rein, das in das EEPROM geschrieben werden soll
EECR (EEProm Control Register): Das Steuerregister für das EEProm.
EEARL und EEARH (EEProm Adress Register) Hier kommen das higher und das lower Byte der anzusprechenden Adresse rein.

Vier Register, kein neuer Befehl, dennoch wartet eine Menge Arbeit auf uns und zu Beginn wollen wir das EEPROM beschreiben. Zunächst und in der Theorie wollen wir den Wert 0x55 an die Adresse 0x00FF im EEPROM schreiben.

Bevor wir hier irgendwelche Registerinhalte verändern müssen wir sicherstellen, dass kein Schreibvorgang aktiv ist. Dazu schauen wir in das EEPROM Control Register und interessieren uns für das Bit 1 namens EEWE (EEProm Write Enable). Mit diesem wird nämlich der Schreibvorgang ausgelöst und es wird zurückgesetzt, wenn dieser beendet ist. Es geht also wie folgt los:

Code: Alles auswählen

Schreibe_EEProm:       ; Unterprogramm zum Beschreiben des EEPROM
sbic EECR, EEWE         ; ist der letzte Schreibvorgang beendet?
rjmp Schreibe_EEProm  ; Wenn nein, dann nochmal prüfen
Als nächstes schreiben wir die Zieladressen in die entsprechenden Register ebenso das zu speichernde Byte. Da wir ja die Adresse 0x00FF vorgegeben haben muss das Higher Byte, also der Wert 0x00 in das Register EEARH, das lower Byter, also 0xFF in das Register EEARL.
Unser Datenbyte, der Wert 0x55 kommt in das Register EEDR und nachdem wir nun sicher sind, dass kein Schreibvorgang offen ist können wir fortfahren:

Code: Alles auswählen

ldi Temp, 0x00     ; Higher Byte ist 0x00
out EEARH, Temp ; Schreibe Higher Byte in Adressregister

ldi Temp, 0xFF      ; Lower Byte ist 0xFF
out EEARL, Temp  ; Schreibe Lower Byte in Adressregister

ldi Temp, 0x55     ; unser zu sicherndes Byte
out EEDR, Temp   ; Schreibe das Datenbyte ins Datenregister
So, jetzt ist alles vorbereitet, jetzt wird es ernst. Da das Einleiten des Schreibvorgangs auf keinen Fall unterbrochen werden darf bietet es sich dringendst an, Interupts zu sperren. Das Beschreiben des EEPROMS starten wir durch das Setzen zweier Bits im EECR. Zuerst wird das Bit EEPROM Master Write Enabled gesetzt, anschliessend der Schreibvorgang durch das Setzen des EEProm Write Enable gestartet.

Code: Alles auswählen

cli                          ; Alle Interupts sperren, die folgenden beiden Befehle dürfen nicht unterbrochen werden!
sbi EECR, EEMWE   ; Master Write Enable setzen
sbi EECR, EEWE     ; Write Enable setzen und Schreibvorgang starten
sei                        ; Interupts erlauben
Damit wird der Schreibvorgang ausgelöst, nach dessen Beendigung werden die Bits EEMWE und EEWE vom Prozessor gelöscht und signalisieren damit die Bereitschaft für den evtl. nächsten Schreibvorgang.

Nun steht also unser Wert 0x55 in der Adresse 0x00FF des EEPROM und bleibt da auch nach dem Ausschalten stehen.
Damit wir davon etwas haben wollen wir uns nun ans Auslesen machen und damit wir das EEPROM nicht unnötig stören beginnen wir hier genauso wie beim Schreiben mit der Abfrage, ob gerade ein Schreibvorgang aktiv ist:

Code: Alles auswählen

Lese_EEProm:       ; Unterprogramm zum Auslesen des EEPROM
sbic EECR, EEWE         ; ist der letzte Schreibvorgang beendet?
rjmp Lese_EEProm      ; Wenn nein, dann nochmal prüfen
Als nächstes legen wir fest, aus welcher Adresse gelesen werden soll... wir wissen das ja ;)

Code: Alles auswählen

ldi Temp, 0x00     ; Higher Byte ist 0x00
out EEARH, Temp ; Schreibe Higher Byte in Adressregister

ldi Temp, 0xFF      ; Lower Byte ist 0xFF
out EEARL, Temp  ; Schreibe Lower Byte in Adressregister
Das Lesen ist nicht so kritisch wie das Schreiben, also können wir den Lesevorgang gleich einleiten, in dem wir das Bit EERE (EEProm Read Enable) im EEPROM Control Register setzen. Das ausgelesene Byte steht dann... im EEPROM Data Register EEDR:

Code: Alles auswählen

sbi EECR, EERE  ; Lesevorgang einleiten
in Temp, EEDR   ; Ausgelesenes Byte nach Temp schreiben
Bevor wir uns an ein Beispiel machen noch ein Hinweis: Das EEPROM kann beliebig oft ausgelesen, jedoch "nur" ca. 100.000 mal beschrieben werden. Klingt nach unereichbar, ist es aber nicht. Geht es z.B. darum, einen Zustand sehr oft zu ermitteln, dann sollte man sich gut überlegen, ob das Ergebnis immer in den EEPROM muss. So wäre es z.B. ein Prozessorselbstmord auf Raten, die Fahrstufen eines Decoders stets ins EEPROM zu legen... denn jedes Hoch-Runterregeln würde die Lebensdauer des EEPROM und damit des Decoders verkürzen.

Heute habe ich ein lustiges Programm für Euch, da steckt auch die eine oder andere Überaschung drin. Erstmal die Vorgabe:
Nach dem ersten Einschalten sollen alle LED leuchten.
Mit T1 schaltet man eine LED dunkel und schiebt diese nach links.
Mit T2 wird der aktuelle Zustand gespeichert
Wird die Schaltung stromlos geschalten und wieder in Betrieb genommen ist das zuletzt gespeicherte Bitmuster anzuzeigen und es kann mit T1 an dieser Stelle weitergeschalten werden.

Nun, im Prinzip keine grosse Aufgabe. Nach dem Initialisieren lesen wir das Bitmuster aus dem EEPROM ein und geben es an die LEDs aus.
Mit T1 gehen wir in ein Unterprogramm, welches das Bitmuster ändert und ausgibt.
Mit T2 gehen wir in ein Unterprogramm, welches das Bitmuster in das EEPROM schreibt.
Bitte das Fusebit EESAVE nicht aktivieren, damit wird unser EEProm immer brav zurückgesetzt.

Nun, warum ist das Programm lustig? Es wird einen kleinen Fehler enthalten, den Ihr mir bitte erkärt :twisted:
Es gibt übrigens auch einen Grund, warum am Anfang alle LEDs an sind... im Auslieferungszustand sind die Bytes im EEPROM alle mit 0xFF beschrieben und so muss ich hier keine Klimmzüge machen. Am Anfang sind also alle an... bis das erste Bitmuster gespeichert ist ;)

Bitte das Programm als neues Projekt anlegen, compilieren und auf Euere Platinen laden... viel Spass beim Fehlersuchen, ja der ist wirklich kniffelig ;)

Code: Alles auswählen

; Lesen und Schreiben im EEPROM

; Unser Datenbyte liegt im EEPROM auf Adresse 0x0102
; T1 schaltet LEDs weiter
; T2 speichert aktuellen Zustand


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


.def Temp = r19
.def Adresse_L = r20
.def Adresse_H = r21


.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


      
      out portb, r16               ; Alle LEDs ein

	  ldi Adresse_H, 0x01			; HigherByte der EEPROM-Adresse ablegen
	  ldi Adresse_L, 0x02			; LowerByte der EEPROM-Adresse ablegen

	; Laden des letzten Zustands



	Lese_EEProm:					; Unterprogramm zum Auslesen des EEPROM
	sbic EECR, EEWE         		; ist der letzte Schreibvorgang beendet?
	rjmp Lese_EEProm      			; Wenn nein, dann nochmal prüfen

	out EEARH, Adresse_H			; HigherByte in Register
	out EEARL, Adresse_L			; LowerByte in Register

	sbi EECR, EERE         			; Lesevorgang einleiten
	in Temp, EEDR   				; Ausgelesenes Byte nach Temp schreiben


	

	out portb, Temp					; gespeichertes Bitmuster ausgeben



	Main:							; Hauptprogramm
	sbis Pind, 2					; Ist T1 betätigt?
	rcall Bitmuster					; Wenn ja, verändere das Bitmuster
	sbis Pind, 3					; Ist T2 betätigt?
	rcall Schreibe_EEPROM			; Wenn ja, dann schreibe EEPROM
	rjmp Main						; Endlosschleife



	Bitmuster:						; UP zum Verändern des Bitmusters
	sbis Pind, 2					; Ist T1 noch betätigt?
	rjmp Bitmuster					; Wenn ja, nochmal abfragen
	rol Temp						; Inhalt von Temp nach links schieben
	out Portb, Temp					; Bitmuster ausgeben
	ret								; Ende Unterprogramm


	Schreibe_EEProm:       			; Unterprogramm zum Beschreiben des EEPROM
	sbis Pind, 3					; Ist T2 betätigt?
	rjmp Schreibe_EEProm			; Wenn ja, nochmal abfragen
	sbic EECR, EEWE         		; ist der letzte Schreibvorgang beendet?
	rjmp Schreibe_EEProm  			; Wenn nein, dann nochmal prüfen

	out EEARH, Adresse_H			; HigherByte in Register
	out EEARL, Adresse_L			; LowerByte in Register
	out EEDR, Temp 					  ; Schreibe das Datenbyte ins Datenregister

	cli                          	; Alle Interupts sperren, die folgenden beiden Befehle dürfen nicht unterbrochen werden!
	sbi EECR, EEMWE   				; Master Write Enable setzen
	sbi EECR, EEWE     				; Write Enable setzen und Schreibvorgang starten

	ret								; Ende Unterprogramm
Nochmal wegen dem Fehler... schaut bitte erstmal, ob das Programm das macht, was es soll... und wenn nicht... warum es sich nicht so genau an die Vorgaben hält... Was wir dagegen tun ist etwas anderes, das machen wir dann.

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#108

Beitrag von Muenchner Kindl »

Tasten auswerten und entprellen

Guten Morgen,

ein Ergänzungsthema möche und kann ich Euch nicht ersparen und weil es gerade gut passt drücke ich es Euch an dieser Stelle aufs Auge:
Das Auswerten, Abarbeiten und Entprellen von Tasten.

Es ist leider so, dass wenn man einen mechanischen Kontakt betätigt, dass dieser unmittelbar vor dem Schliessen und nach dem Öffnen prellt. Das bedeutet, dass der Kontakt, z.B. durch Nachfedern der Kontaktflächen beim Öffnen, mehrmals zustande kommt. Benutzt man einen Taster z.B. dazu, einen Zähler um 1 zu erhöhen kann es passieren, dass der Zähler beim Betätigen und Loslassen springt, was den Anwender des Programms sicher nicht gefallen wird.

Es gibt zwei Möglichkeiten, Tasten zu entprellen:

- Hardware
Es gibt zum Einen hochwertigere, prellfreie und teuerere Tasten, in der Regel wird aber parallel zum Kontakt ein Kondensator eingebaut. Bei unseren UL-E sind die beiden Taster mit einem 100nF-Kondensator entprellt, was für unsere Zwecke locker ausreicht.
In der Serienfertigung ist es allerdings so, dass selbst so ein windiger Kondensator ein Kostenfaktor ist. Dieser verteuert nicht nur selbst die Herstellung, auch die Herstellung der Platine wird mit jedem Kondensator teuerer und so lässt sich der eine oder andere Euro durchaus einsparen, wenn man auf die hardwaremäßige Entprellung verzichtet, was in der Regel auch der Fall ist. Die beiden entprellten Taster von Schönwitz sind also quasi sowas wie Luxus, in der Praxis wird Euch das Leben so leicht nicht gemacht, da gibt es nur die zweite Möglichkeit...

- Software
Das Entprellen von Tasten bietet genug Stoff für einen einen eigenen Workshop, zusammen mit Grundsatzdiskussionen und Meinungsaustausch. Es gibt sehr sehr viele Wege, Tasten softwaretechnisch zu entprellen, leider keinen Königsweg und oft ist es auch eine Frage der Anwendung, wie entprellt werden soll oder darf.
Grundsätzlich geht es darum, das Schwingen und Federn des Kontaktes abzufangen und auszublenden und hierbei spielt Zeit eine Rolle.
Eine Lösung kann also wie folgt aussehen:
- Kontakt wird betätigt
- Es wird eine Zeit (wenige ms) abgewartet
- Der Kontakt wird nochmal zur Bestätigung abgefragt
- Die dem Kontakt entsprechende Funktion wird ausgeführt

Wer auf Nummer Sicher gehen will führt die Funktion erst aus, wenn der Kontakt wieder geöffnet wird, die Taste also losgelassen wird:
- Kontakt wird betätigt
- Es wird eine Zeit (wenige ms) abgewartet
- Der Kontakt wird nochmal zur Bestätigung abgefragt
- Kontakt wird geöffnet
- Es wird eine Zeit (wenige ms) abgewartet
- Der Kontakt wird nochmals zur Bestätigung abgefragt
- Die Funktion wird ausgeführt

Wie gesagt, eine theoretische Möglichkeit, damit verweise ich auf das entsprechende Thema im Tutorial von http://www.mikrocontroller.net

Wir werden das Thema Entprellen nicht weiter behandeln, da uns der Schönwitz mit zwei Kondensatoren reich beschenkt hat, vielleicht wäre das aber tatsächlich einen weiteren Workshop wert ;)

Dennoch möchte ich noch darauf eingehen, wie ich die Tasten auswerte und dazu verweise ich auf das Demoprogramm des Themas "EEPROM".

Hier frage ich im Hauptprogramm innerhalb einer Endlosschleife ständig die Eingabeports, bzw. die entsprechenden Bits ab und springe, ist das jeweilige Bit gelöscht (die Taster sind Lowaktiv!) in das zugehörige Unterprogramm.

Code: Alles auswählen

   Main:                     ; Hauptprogramm
   sbis Pind, 2               ; Ist T1 betätigt?
   rcall Bitmuster               ; Wenn ja, verändere das Bitmuster
   sbis Pind, 3               ; Ist T2 betätigt?
   rcall Schreibe_EEPROM         ; Wenn ja, dann schreibe EEPROM
   rjmp Main                  ; Endlosschleife
Im Unterprogramm warte ich dann, vor dessen Abarbeitung, noch ab, dass die Taste wieder losgelassen wird.

Code: Alles auswählen

   Bitmuster:                  ; UP zum Verändern des Bitmusters
   sbis Pind, 2               ; Ist T1 noch betätigt?
   rjmp Bitmuster               ; Wenn ja, nochmal abfragen
Für hardwaremäßig entprellte Tasten reicht das völlig aus, ohne die Kondensatoren hätten wir damit nicht ganz so viel Freude.

Entprellte Grüße und schonmal ein schönes Wochenende ;)

kschwi
InterRegio (IR)
Beiträge: 107
Registriert: Mo 19. Feb 2007, 10:01
Wohnort: Seelze

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#109

Beitrag von kschwi »

Hallo zusammen,
hier noch eine Präzisierung:
Muenchner Kindl hat geschrieben:Das EEPROM kann beliebig oft ausgelesen, jedoch "nur" ca. 100.000 mal beschrieben werden.
Es geht hier nicht um die Summenanzahl aller Schreibzyklen im EEPROM sondern um der Lösch- / Schreibzyklen für eine Zelle / Adresse. Unter dem Titel AVR101: High Endurance EEPROM Storage findet man bei Atmel eine Beschreibung, wie man bei Bedarf durch einen trickreichen Algorithmus und einige weitere Zellen im EEPROM diese Anzahl verlängern kann.

Off topic: Auch Flash Speicher haben eine begrenzte Lebensdauer - spielt beim AVR meist keine Rolle - aber USB-Sticks sind auch Flash-Speicher. Dort wird auch getrickst , dass möglichst alle Zellen mal dran kommen, nur auch hier ist irgendwann unwiederbringlich Schluss mit lustig.

Ciao
Knut

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#110

Beitrag von Muenchner Kindl »

Hallo Knut,

vielen Dank für Deine Ergänzung und Präzisierung :-)

Ich werde jetzt mal zusehen, das nächste Thema vorzubereiten...

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#111

Beitrag von Muenchner Kindl »

Der Analog-Digital-Converter ADC

Hallo,

bezüglich der Grundlagen ist dies das letzte Thema, welches wir behandeln, bevor wir mit unserem eigentlichen Projekt beginnen. Es kommen auch hier keine neuen Befehle mehr vor, es geht lediglich noch um das Beschreiben und Auslesen verschiedener Steuerregister.
Bezüglich der Befehle kann ich Euch sagen, dass wir nicht alle durchgenommen haben. Als Handwerkszeug für die meisten Anwendungen reicht das aber locker, wem das nicht reicht hat hier eine gute Übersicht. Auch das Datenblatt sollte der ständige Begleiter bei einem Projekt sein und was immer hilft ist das Tutorial von Mikrocontroller.net. Ferner helfen Google und Co immer weiter... aber Vorsicht... ich habe auch schon die eine oder andere Befehlsübersicht gefunden, bei denen die Syntax falsch dargestellt wurde. Im Zweifelsfall haben immer die Unterlagen der Herstellers Priorität, auch wenn sie in Englisch verfasst sind.

Nun, im Prinzip müsste dieses Thema mit ein paar Grundlagen beginnen, für die ich jedoch auf das entsprechende Thema im Tutorial verweise.
Unsere UL-E Platine arbeitet mit der internen Referenzspannung und wenn es darum geht, ein Potentiometer oder z.B. einen Photowiderstand auszuwerten, ist diese Schaltung auch das geeignete Mittel zum Zweck.

Um unsere drei Potis also benutzen zu können müssen wir über diese elektronischen Grundkenntnisse nicht zwingend verfügen. Wer etwas basteln möchte kann sich entweder an der Schönwitzplatine orientieren oder muss tiefer in die Materie einsteigen.

Wir beschäftigen uns hier ausschliesslich mit den Steuerregistern und deren Verwendung und auf Seite 189 unserers schlauen Buches werden die Hintergründe erleutert.
Nun, irgendwo in diesem Workshop habe ich von 3 ADC-Kanälen gesprochen, die der ATM8 unterstützt. Das trifft zwar auf die UL-Platine zu, ist aber für den Prozessor falsch, der besitzt 6 bzw. 8 Kanäle (abhängig von der Gehäusebauform) :shock:

Und da sind sie auch schon... unsere heiss geliebten Steuerregister... und wir beginnen mit dem Register ADMUX:
Dieses steuert zunächst über die Bits0-3, welcher der 6/8 Kanäle angesprochen wird. Ausserdem ist hier die Referenz festgelegt und zuguterletzt die Darstellung des Ergebnisses.
Alle ADC-Kanäle liefern nämlich ein 10 Bit grosses Ergebnis, aufgeteilt in zwei Register und hier geht es darum, das Ergebnis links- oder rechtsbündig in die beiden Register zu schreiben.
Im Detail sieht das Steuerregister ADMUX wie folgt aus:

Bit0 = MUX0 (ADC-Kanal)
Bit1 = MUX1 (ADC-Kanal)
Bit2 = MUX2 (ADC-Kanal)
Bit3 = MUX3 (ADC-Kanal)
Bit4 = unbenutzt
Bit5 = ADLAR (Legt fest, ob Ergebnis rechts- oder linksbündig abgelegt wird)
Bit6 = REFS0 (Einstellung zur Referenz)
Bit7= REFS1 (Einstellung zur Referenz)

Während der Kanal 0-7 in den MUX-Bits einfach einzustellen ist (0000 - 0111) müssen wir für die anderen Bits nochmal ins Datenblatt schauen, und zwar auf Seite 199.

Wollen wir das Poti 1 auf der Platine, angeschlossen an Kanal 0, auslesen müssen wir vorher das Register ADMUX wie folgt beschreiben:

Code: Alles auswählen

ldi r17, 0b01000000 
out ADMUX, r17
Aufgelöst sieht das, begonnen mit Bit7 wie folgt aus:
01 = interne Referenz wird genutzt
0 = Das Ergebnis wird rechtsbündig ausgerichtet
0 = ungenutztes Bit
000 = Kanal 0

Grundlegend haben wir den AD-Converter damit eingestellt, ein paar Dinge fehlen aber dennoch und dafür gibt es ein zweites Steuerregister... beschrieben auch im Datenblatt Seite 200.
Unser Blick fällt hier auf das Register ADCSRA und seinen 8 Bits, welche wie folgt beschrieben sind:

Bit0 = ADPS0 (Vorteiler)
Bit1 = ADPS1 (Vorteiler)
Bit2 = ADPS2 (Vorteiler)
Bit3 = ADIE (AD Interupt Enable)
Bit4 = ADIF (AD Interupt Flag)
Bit5 = ADFR (AD Free Running)
Bit6 = ADSC (AD Start Conversation)
Bit7 = ADEN (AD Enable)

Bitte nicht erschrecken, das schaut schlimmer aus als es ist.

- Mit dem Bit7 (ADEN) wird der ADC (definiert in ADMUX ;) ) ein/ausgeschalten. Mit einer 1 in Bit7 ist der ADC ein.
- Ein gesetztes Bit6 ADSC startet die Conversation, also die Messung. Nach der Wandlung wird dieses Bit wieder 0.
- Mit einer 1 in Bit5 (ADFR) ist das Starten via Bit6 nur einmal erforderlich. Im FreeRunning-Modus wird fortwährend gemessen.
- Bit4 (ADIF) ist ein Flag, welches gesetzt wird, wenn eine Messung abgeschlossen ist
- Ist Bit3 (ADIE) gesetzt wird nach einer erfolgten Messung ein Interrupt ausgelöst und zur entsprechenden Adresse gesprungen.
- Mit den Bits0-2 ist ein Vorteiler für die ADC-Frequenz einzustellen. Diese liegt idealerweise zwischen 50 und 200kHZ. Da unsere Prozessoren mit 8MHz takten sollten wir hier also einen Teiler von 64 (=125kHZ) oder 128 (=62,5kHZ) einstellen. Die Tabelle dazu steht im Datenblatt auf Seite 201.

Nun stellen wir unseren ADC0, den wir ja bereits oben grundlegend definiert haben, mal so ein, dass ein Ergebnis bekommen.

Code: Alles auswählen

ldi r17, 0b11000110
out ADCSRA, r17
Aufgedröselt schaut das so aus:
Bit7 = 1 -> ADC einschalten
Bit6 = 1 -> Messung starten
Bit5 = 0 -> kein FreeRunning
Bit4 = 0 -> Wird vom System gesetzt
Bit3 = 0 -> kein Interrupt
Bit0-2 = 110 -> Prescaler 64 = 125kHZ

Ist die Messung abgeschlossen gibt es ein 10 Bit breites Ergebnis. Da ein Register ja nur 8 Bit besitzt brauchen wir also zwei Register, namentlich ADCH und ADCL.
Je nachdem ob das Bit5 (ADLAR) im Register ADMUX gesetzt ist oder nicht wird das Ergebnis rechts- oder linksbündig in diesen beiden Registern abgelegt.

ADLAR in ADMUX = 0 -> Ergebnis ist rechtsbündig:
Die beiden höherwertigen Bits (Bit8 und 9) werden in Bit0 und Bit1 des Registers ADCH abgelegt, die acht anderen Bits entsprechend ihrer Wertigkeit in ADCL.
000000EE EEEEEEEE

ADLAR in ADMUX = 1 -> Ergebnis ist linksbündig:
Die acht höchstwertigen Bits liegen im Register ADCH, Bit0 und Bit1 des Ergebnisses auf Bit6 und Bit7 von ADCL.
EEEEEEEE EE000000

Wenn uns vom Ergebnis ein Byte zur Auswertung reicht legen wir hier also fest, wie grob oder fein unsere Abfrage ist.
Jedoch müssen, auch wenn nur ein Register benötigt wird, immer beide Register ausgelesen werden, und zwar zuerst ADCL und dann ADCH!

Benötigen wir nur ADCH und ist das Ergebnis laut ADEN linksbündig müssen wir wie folgt vorgehen:

Code: Alles auswählen

in r17, ADCL ; Auslesen von ADCL, Ergebnis wird durch Überschreiben verworfen
in r17, ADCH ; Auslesen von ADCH zur weiteren Verwendung
Ist das Ergebnis rechtsbündig und wir brauchen nur ADCL, dann sieht das in etwa so aus:

Code: Alles auswählen

push r18      ; Sichern von r18
in r17, ADCL ; Einlesen von ADCL
in r18, ADCH ; Einlesen von ADCH, wird wieder verworfen
pop r18        ; Zurücksichern von r18 und Verwerfen von ADCH
Kommen wir nun also zur Praxis. Zur Anschauung ein einfaches Programm, welches das Poti P1 ausliest und das Ergebnis auf dem PortB ausgibt:

Code: Alles auswählen

; ADC Test1

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

.def Temp = r16
.def Ergebnis = 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

		; 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


Main:

		ldi temp, 0b01000000			; ADC-Kanal0, int. Referenz, rechtsbündig
		out ADMUX, temp					; Definition in ADMUX schreiben

Einlesen:
		ldi temp, 0b11000111			; ADC einschalten und Messung starten
		out ADCSRA, Temp				; Steuerregister beschreiben
Warten:
		sbis ADCSRA, ADIF				; Ist Messung abgeschlossen?
		rjmp Warten						; Wenn nein, nochmal abfragen

		in Ergebnis, ADCL				; ADCL nach Ergebnis einlesen
		in temp, ADCH					; ADCH einlesen, wird nicht verarbeitet

		out portb, Ergebnis				; Ergebnis nach PortB ausgeben

		rjmp Einlesen					; Endlosschleife
Kopiert Euch das bitte in ein eigenes Projekt und experimentiert gerne damit.

Etwas vereinfacht sieht das Programm so aus:

Code: Alles auswählen

; ADC Test2

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

.def Temp = r16
.def Ergebnis = 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

		; 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


Main:

		ldi temp, 0b01000000			; ADC-Kanal0, int. Referenz, rechtsbündig
		out ADMUX, temp					; Definition in ADMUX schreiben
		ldi temp, 0b11100111			; ADC einschalten und Messung FreeRunning starten
		out ADCSRA, Temp				; Steuerregister beschreiben

Warten:
		in Ergebnis, ADCL				; ADCL nach Ergebnis einlesen
		in temp, ADCH					; ADCH einlesen, wird nicht verarbeitet

		out portb, Ergebnis				; Ergebnis nach PortB ausgeben

		rjmp Warten					; Endlosschleife
Beim ersten Programm (ADC Test1) wird der ADC bei jedem Durchlauf eingeschalten, während der ADC im zweiten Programm (ADC Test2) im FreeRunning-Modus betrieben wird.
Wird nur ein ADC-Kanal benötigt, dann kann der FreeRunning-Modus verwendet werden. Müssen mehrere Kanäle abgefragt werden müssen diese jeweils via ADMUX definiert und dann einzeln abgefragt werden.

Wem das Ergebnis zu zappelig ist... einfach folgende Zeilen ändern:

ldi temp, 0b01100000 ; ADC-Kanal0, int. Referenz, linksbündig

in Temp, ADCL ; ADCL einlesen, wird nicht verarbeitet
in Ergebnis, ADCH ; ADCH nach Ergebnis einlesen

Nach dieser Änderung wird das Ergebnis linksbündig in die beiden Ergebnisregister geschrieben und die höherwertigen 8 Bits ausgewertet.


Mit dem ADC haben wir nun den letzten Baustein für unsere Ampel in der Hand. Experimentiert bitte ein wenig mit den beiden Programmen. Wenn es Fragen gibt, her damit ;-)

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#112

Beitrag von Muenchner Kindl »

Kleiner Nachtrag:

Mit den beiden Programmen zum ADC kann die Schaltung als (extrem) einfaches Ohmmeter verwendet werden. Einfach den zu messenden Widerstand zwischen die Klemmen "E3" und "-" klemmen.
Ich habe stattdessen einen LDR (Photowiderstand) angeschlossen und damit quasi ein Helligkeitswertschätzeisen gebaut... zu Demozwecken reicht es allemal ;)

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#113

Beitrag von Muenchner Kindl »

Noch ein Nachtrag... eben mal schnell aus Langeweile programmiert:

Code: Alles auswählen

; einstellbares Blinklicht mit Timer0

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

.def Time1 = r16
.def Temp = r17

.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

		rcall ADC0_Lesen				; Ersten Zeitwert ermitteln

		sei								; Interrupts erlauben

Hauptprogramm:
		rjmp Hauptprogramm				; Das Hauptprogramm ist nur eine Endlosschleife



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

		rcall ADC0_Lesen				; Poti einlesen
										
		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


ADC0_Lesen:
		push temp						; Register Temp retten
		ldi temp, 0b01100000            ; ADC-Kanal0, int. Referenz, linksbündig
		out ADMUX, temp                 ; Definition in ADMUX schreiben

Einlesen:
		ldi temp, 0b11000111            ; ADC einschalten und Messung starten
		out ADCSRA, Temp           		; Steuerregister beschreiben
Warten:
		sbis ADCSRA, ADIF               ; Ist Messung abgeschlossen?
		rjmp Warten                     ; Wenn nein, nochmal abfragen

		in Temp, ADCL                   ; ADCL nach Ergebnis einlesen
		in Time1, ADCH                  ; ADCH einlesen, wird nicht verarbeitet
		pop temp						; Register Temp zurückholen
		ret								; Ende Unterprogramm
Es ist eine Kombination aus dem Timerexperiment und dem ADC-Thema... nun könnt Ihr die Blinkfrequenz mit P1 einstellen ;)
Benutzeravatar

norbertk
Regionalbahn (RB)
Beiträge: 36
Registriert: Di 14. Dez 2010, 13:16
Nenngröße: TT
Stromart: DC
Steuerung: TAMS MC + ROCRAIL
Gleise: Tillig-Standard
Wohnort: Chemnitz
Kontaktdaten:

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#114

Beitrag von norbertk »

Hallo Thomas

Da ich mir erst jetzt einen AVR Programmer (mySmartusb Light) und einem ATM8 zugelegt habe, bin ich dabei den Stoff aus deinem Super Workshop zu inhalieren. Experimentiert wird auf einem Steckboard. Komme ganz gut damit klar. Der Workshop ist gut strukturiert und sehr gut erklärt. Vielen Dank. Hätte da noch ein paar Wünsche bzw. Vorschläge: Kannst du bitte auch mal auf die Debug-Möglichkeiten des AVR Studios bei einem Beispiel mit einbinden ? Ein weiterer Traum wäre auch mal, das DCC Signal mit einem AVR zu "zerlegen" und auszuwerten, um mal sowas wie einen Funktionsdekoder oder Weichendekoder zu "bauen". Natürlich nur, wenn das andere auch wünschen.

Also noch mal großes Lob und vielen Dank
Gruß Norbert
Windows 10 - Rocrail - Tams Master Control - Tams B3 Booster - Spur TT - Epoche IV

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#115

Beitrag von Muenchner Kindl »

Hallo Norbert,

erstmal vielen Dank für das Kompliment!
Kannst du bitte auch mal auf die Debug-Möglichkeiten des AVR Studios bei einem Beispiel mit einbinden ? Ein weiterer Traum wäre auch mal, das DCC Signal mit einem AVR zu "zerlegen" und auszuwerten, um mal sowas wie einen Funktionsdekoder oder Weichendekoder zu "bauen". Natürlich nur, wenn das andere auch wünschen.
Hmmm... das sind ja drei Wünsche auf einmal :zahnlos:

Im Ernst: Die Debug-Funktionen nutze ich so gut wie gar nicht, deshalb fällt es mir hier etwas schwer, etwas zu vermitteln. Für mich persönlich ist das AVM-Studio so, wie ich es benutze, schon reiner Luxus, ich habe Assembler mit einem Einplatinencomputer gelernt... der kannte nur Mnemocode, also Hexzahlen.

Die Geschichte mit dem DCC-Signal zerlegen ist sicher reizvoll, allerdings dürfte das nicht mit unserer Hardwarebasis funktionieren. Da ist dann schon einiges mehr notwendig und neben der Tatsache, dass ich auch hier nicht über die nötigen Erfahrungen verfüge möchte ich nur ungern Leute zu Bastelarbeiten an deren Systemen anleiteiten. Unterläuft mir bei der Basis dieses Workshops ein Fehler funktioniert es einfach nicht. Passiert mir das beim Anleiten der Hardware und es raucht jemanden ein Booster oder seine CS2 ab, dann hätte ich zumindest ein schlechtes Gewissen... nein, das möchte ich nicht machen.

Zum Workshop selbst... ich hoffe, es ist OK, dass ich den die letzten beiden und evtl. das kommende Wochenende pausieren lasse. Neben vielen anderen zu erledigenden Aufgaben denke ich, es sollte jeder die Chance haben, den thematisch quasi abgeschlossenen Workshop langsam aufzunehmen, bevor wir unser Projekt beginnen.

Nochmals danke für das Lob und viele Grüße,

Thomas

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#116

Beitrag von Muenchner Kindl »

Projekt Verkehrsampel Grundüberlegungen

Nun, bevor wir uns die Finger am eigentlichen Programm wundschreiben sollten wir uns ein paar Dinge übelegen, wie wir zur Lösung unseres Projekts kommen.

Zunächst, was wollen wir?

Vorgabe:
1- Es ist eine Verkehrsampel zu realisieren, welche mit T1 zwischen Fussgängerampel mit Anforderungstaste und Verkehrsampel zur Kreuzungssicherung umschaltbar ist. Die jeweilige Einstellung soll gespeichert werden.

2- Die Rot-Phase soll mit P1 einstellbar sein
3- Die Gelb-Phase soll mit P2 einstellbar sein
3- Die Wartezeit für die Fussgängerampel soll mit P3 einstellbar sein

Lösungsansätze:
1. es ist ein Statusbyte zu reservieren, welches bei der Initialisierung mit vorher festzulegenden Byte aus dem EEPROM befüllt wird. Während dem Programmablauf wird T1 ständig abgefragt. Bei Betätigung wird das Statusbyte verändert und sofort im EEPROM gesichert.

2-3 Die Potis werden während dem Programmablauf ständig ausgelesen und die Ergebnisse in einem dafür vorgegebenen Register abgelegt.

4. Die Zeitsteuerung wird mit einem Timer realisiert

4.1. Bei Kreuzungsampel läuft der Timer ständig und es werden laufend die Signalbilder angezeigt. T2 aktiviert/deaktiviert den Timer und schaltet die Ampel ein/aus

4.2. Bei Fussgängerampel wird der Timer mit T2 aktiviert, nach Durchlauf der Grünphase für Fussgänger wird der Timer wieder deaktiviert

5. Die Signalbilder werden zentral im DSEG abgelegt

Wer will kann schonmal erste Vorschläge unterbreiten, Fragen stellen oder vielleicht andere/bessere Ideen zur Lösung beitragen.
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)

#117

Beitrag von DeMorpheus »

Hallo,

ich werde erstmal das Projekt nur verfolgen können, ich hab im Moment Klausurphase und dementsprechend „besseres“ zu tun.
Aber da das Ganze ja nicht in einer Woche fertig ist, bin ich ganz zuversichtlich da noch mitwirken zu können. ;)

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)

#118

Beitrag von kaeselok »

Oje, die Verkehrsampel ... :oops: unser Gesellenstück! :cry:

Ich glaube ich bin auch noch nicht so weit ... aber ich kann ja darüber mal brüten :-)

Eine Deutsche Ampel, ja? Also nach Rot, kommt Rot-Gelb und dann erst Grün, nicht gleich nach Rot folgt unmittelbar Grün, richtig? (US-Version)

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)

#119

Beitrag von Muenchner Kindl »

Hi,
Ich glaube ich bin auch noch nicht so weit
Wir machen das zusammen und im Prinzip werden so gut wie alle bisher besprochenen Themen wiederholt und vertieft. Und... (ich bin auch noch nicht so weit)... tatsächlich gibt es von unserem Gesellenstück noch nicht mehr als hier zu lesen ist... wir machen das wirklich zusammen :)

Moritz, Dir viel Erfolg in der K-Phase !

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#120

Beitrag von Muenchner Kindl »

Initialisierung und Hauptprogramm Teil 1

Hallo,

wir beginnen unser "Gesellenstück" langsam und mit Bedacht und so fangen wir mal ganz in Ruhe mit der Initialisierung und dem ersten Teil des Hauptprogrammes an.

Wir werde dabei vor allem am Anfang mit Test-Unterprogrammen arbeiten, um zu sehen, ob die geschriebenen Module auch funktionieren, es wird also immer ein Ergebnis geben ;)

Ich rate Euch zudem, für jeden Teil in ein eigenes Projekt anzulegen. Ich nenne das z.B. Ampelprojekt_1 für die erste Version, Ampelprojekt_2 für die zweite usw.
Damit ist nachvollziehbar, was wir wie wann und warum gemacht haben.

Beginnen wir also mit der Initialisierung.
Eigentlich kein grosses Hexenwerk, wir reservieren uns ein paar Variablen, definieren unsere Ein- und Ausgänge und stellen unseren Timer ein... ich habe eigentlich alles aus den vielen Beiträgen vorher kopiert:

Code: Alles auswählen

; Workshop Projekt Ampel
; Autoren: Teilnehmer des Assemblerworkshops

; Version 1

; Vorgaben:
; EEPROM-Adresse für Statusbyte: 0x00FF



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

.def Time1 = r16					; Zeitvariable
.def Temp = r20						; temporäre Variable
.def Status = r21					; Statusbyte

.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 Temp, 0x00               ; alle Bits in Temp auf 0 setzen
	out ddrd, Temp               ; Alle Pins von Port D sind Eingang

	ldi Temp, 0xFF               ; Alle Bits ind Temp auf 1 setzen
	out portd, Temp               ; PC2 und PC3 bekommen PullUp-Widerstände

	; Initialisieren der Ausgänge
	out ddrb, Temp               ; Alle Pins von Port B sind Ausgang (Temp ist noch 0xFF)


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

      

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

	ldi Temp, 0b00000001				; TimerOverflow 0 einschalten
	out TIMSK, Temp
Eine Kleinigkeit kommt noch hinzu, nämlich das Einlesen des Betriebsmodus aus dem EEPROM. Ganz am Anfang habe ich die dafür vorgesehene Adresse vermerkt und damit unser Programm den Betriebsstatus kennt muss das Auslesen dieses Bytes ebenfalls in die Initialisierung.
Danach werden mit "sei" Interrupts erlaubt und das eigentliche Programm kann gestartet werden.

Code: Alles auswählen

	; Einlesen des Statusbytes
	
	Lese_EEProm:       				; Unterprogramm zum Auslesen des EEPROM
	sbic EECR, EEWE         		; ist der letzte Schreibvorgang beendet?
	rjmp Lese_EEProm      			; Wenn nein, dann nochmal prüfen

	ldi Temp, 0x00     				; Higher Byte ist 0x00
	out EEARH, Temp 				; Schreibe Higher Byte in Adressregister

	ldi Temp, 0xFF      			; Lower Byte ist 0xFF
	out EEARL, Temp  				; Schreibe Lower Byte in Adressregister

	sbi EECR, EERE  				; Lesevorgang einleiten
	in Status, EEDR   				; Ausgelesenes Byte nach Status schreiben
	andi Status, 0x01				; Bit0 ist Statusbit, alle anderen löschen
	sei                        		; Interrupts erlauben
Nach dem Einlesen des Statusbytes wird dieses Maskiert. Wir wissen nicht, was das EEPROM am Anfang liefert, deshalb löschen wir alle Bits, ausser Bit0.

Nachdem sämtliche Aufgaben der Ampel selbst innerhalb der Timerroutine ablaufen beschäftigen wir uns im Hauptprogramm lediglich darum, die Taster und Potis einzulesen.
In Hauptprogramm Teil 1 geht es dabei um den Taster S2, mit dem wir die Betriebsart umschalten. Wir können damit steuern, ob wir eine automatische Verkehrsampel oder eine Fussgängerampel mit Anforderungstaste T1 betreiben.

Code: Alles auswählen

Hauptprogramm:					; Hier beginnt das Hauptprogramm

	sbis Pind, 3					; Ist T2 gedrückt
	rcall Taste2					; Wenn ja, dann Taste2 abarbeiten
	rjmp Hauptprogramm				; Hauptprogramm ist Endlosschleife
Wie man sieht ist das Hauptprogramm (noch) sehr übersichtlich. Wir fragen lediglich ab ob T2 gedrückt ist. Ist dies nicht der Fall startet das Hauptprogramm neu, ist T2 gedrückt wird ein Unterprogramm "Taste2" aufgerufen, welches wir uns als nächstes ansehen:

Code: Alles auswählen

	Taste2:							; Abarbeitung Taste2
	sbis Pind, 3					; Taste 2 losgelassen?
	rjmp Taste2						; Wenn nein, nochmal warten

	cpi Status, 0x01				; Ist Bit0 in Statusregister gesetzt?
	brne Taste2_1					; Wenn nein, springe zum Label Taste2_1

	ldi Status, 0x00				; Lösche Bit0 in Statusregister
	rjmp Taste2_Ende				; Springe zum Ende

	Taste2_1:
	ldi Status, 0x01				; Setze Bit0 in Statusregister

	Taste2_Ende:

	ret								; Ende UP
Hier warten wir erstmal bis T2 wieder losgelassen wurde, sonst wird das Unterprogramm bei längerem Betätigen ständig hintereinander aufgerufen, was wir sicher nicht wollen ;)
Anschliessend vergleichen wir das Statusbyte mit dem Wert 0x01 und befüllen diese mit dem genau entgegengesetzten Wert, also 0x00 bei 0x01 oder 0x01 bei 0x00. Man beachte das Label Taste2_Ende: innerhalb des Unterprogramms. Hier kommen im nächsten Teil die Befehle hin, die den eingestellten Status ins EEPROM schreiben, zum Ausprobieren lassen wir das aber mal weg, die Einstellung wird also nicht gespeichert.

Ein zentraler Bestandteil unseres Programmes wird die Interrupt-Service-Routine für den Timer sein. Diese ist zunächst auch nur äusserst übersichtlich und wird das die nächsten Male auch so bleiben...

Code: Alles auswählen

	Timer_ISR:						; Interuptserviceroutine des Timers
	rcall Test1						; Aufruf von Test1
	reti							; Ende ISR
Es wird also alle 1024 Prozessortakte ein Unterprogramm mit dem Namen Test1 aufgerufen, mit dem wir unser heutiges Ergebnis sichtbar machen können.

Code: Alles auswählen

	Test1:							; Testroutine
	out PortB, Status				; Ausgabe des Statusbytes
	ret								; Ende UP
Test1 bringt dabei lediglich den Inhalt unseres Statusbytes an die LEDs und so können wir mit S2 die LED1 aus- oder einschalten, was später den Betriebsmodus Automatische Ampel oder Anforderungsampel entsprechen wird.

Im Gesamten sieht unser Programm wie folgt aus:

Code: Alles auswählen

; Workshop Projekt Ampel
; Autoren: Teilnehmer des Assemblerworkshops

; Version 1

; Vorgaben:
; EEPROM-Adresse für Statusbyte: 0x00FF



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

.def Time1 = r16					; Zeitvariable
.def Temp = r20						; temporäre Variable
.def Status = r21					; Statusbyte

.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 Temp, 0x00               ; alle Bits in Temp auf 0 setzen
	out ddrd, Temp               ; Alle Pins von Port D sind Eingang

	ldi Temp, 0xFF               ; Alle Bits ind Temp auf 1 setzen
	out portd, Temp               ; PC2 und PC3 bekommen PullUp-Widerstände

	; Initialisieren der Ausgänge
	out ddrb, Temp               ; Alle Pins von Port B sind Ausgang (Temp ist noch 0xFF)


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

      

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

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

	; Einlesen des Statusbytes
	
	Lese_EEProm:       				; Unterprogramm zum Auslesen des EEPROM
	sbic EECR, EEWE         		; ist der letzte Schreibvorgang beendet?
	rjmp Lese_EEProm      			; Wenn nein, dann nochmal prüfen

	ldi Temp, 0x00     				; Higher Byte ist 0x00
	out EEARH, Temp 				; Schreibe Higher Byte in Adressregister

	ldi Temp, 0xFF      			; Lower Byte ist 0xFF
	out EEARL, Temp  				; Schreibe Lower Byte in Adressregister

	sbi EECR, EERE  				; Lesevorgang einleiten
	in Status, EEDR   				; Ausgelesenes Byte nach Status schreiben
	andi Status, 0x01				; Bit0 ist Statusbit, alle anderen löschen
	sei                        		; Interrupts erlauben


	Hauptprogramm:					; Hier beginnt das Hauptprogramm

	sbis Pind, 3					; Ist T2 gedrückt
	rcall Taste2					; Wenn ja, dann Taste2 abarbeiten
	rjmp Hauptprogramm				; Hauptprogramm ist Endlosschleife
	
	
	
	
	Timer_ISR:						; Interuptserviceroutine des Timers
	rcall Test1						; Aufruf von Test1
	reti							; Ende ISR


			
	; Ab hier folgen die Unterprogramme

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

	Taste2:							; Abarbeitung Taste2
	sbis Pind, 3					; Taste 2 losgelassen?
	rjmp Taste2						; Wenn nein, nochmal warten

	cpi Status, 0x01				; Ist Bit0 in Statusregister gesetzt?
	brne Taste2_1					; Wenn nein, springe zum Label Taste2_1

	ldi Status, 0x00				; Lösche Bit0 in Statusregister
	rjmp Taste2_Ende				; Springe zum Ende

	Taste2_1:
	ldi Status, 0x01				; Setze Bit0 in Statusregister

	Taste2_Ende:

	ret								; Ende UP



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

	Test1:							; Testroutine
	out PortB, Status				; Ausgabe des Statusbytes
	ret								; Ende UP

Viel Spass beim Testen, für Fragen ect. stehe ich natürlich gerne zur Verfügung, auch für Verbesserungsvorschläge oder Ideen ;)

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#121

Beitrag von Muenchner Kindl »

Modul Automatikampel und weitere Initialisierung

Hallo,

es gibt wieder Arbeit und damit wollen wir erste praktikable Ergebnisse sehen ;)

Heute geht es darum, die Steuerung für die automatische Verkehrsampel zu implementieren und dazu sind erstmal noch ein paar Überlegungen notwendig.

Zur Lösung gibt es verschiedene Möglichkeiten, ich habe mich für den Aufbau einer "Ampelbilddatenbank" entschieden. Dazu bediene ich mich des Z-Pointers und zwei dadurch definierten Bereichen im SRAM.
Im Bereich 0x0100 - 0x0103 liegen die vier Ampelbilder der Atomatikampel, in 0x0200 - 0x0203 die der Fussgängerampel. Beim Umschalten des Modus muss ich also nur das Higherbyte im Z-Pointer umstellen, ansonsten frage ich einfach nur nacheinander die Inhalte dieser "Datenbank" ab.

Zunächst habe ich weitere Variablen definiert, die uns die Arbeit und Übersicht etwas erleichtern:

Code: Alles auswählen

; Z-Pointer einrichten

.def ZH = r31
.def ZL = r30
Während der Initialisierung rufe ich ein Unterprogramm auf, in dem ich die "Datenbank" befülle:

Code: Alles auswählen

	rcall Init_Datenbank			; Initialisierung der Datenbank
Damit kommen wir zu einem recht grossen Block, nämlich der Initialisierung und Befüllung der Datenbank mit den einzelnen Signalbildern:

Code: Alles auswählen

Init_Datenbank:
									; Ampelbilder Automatikampel

	ldi ZH, 0x01					; 0x01 in Higherbyte von Z-Pointer laden

	ldi ZL, 0x00					; 0x00 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10100001			; Hauptstrasse grün, Nebenstrasse rot
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x01					; 0x01 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10110010			; Hauptstrasse gelb, Nebenstrasse rot-gelb
	st z, Temp						; Ampelbild ablegen	 


	ldi ZL, 0x02					; 0x02 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b01001100			; Hauptstrasse rot, Nebenstrasse grün
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x03					; 0x03 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10010110			; Hauptstrasse rot-gelb, Nebenstrasse gelb
	st z, Temp						; Ampelbild ablegen


									; Ampelbilder Anforderungsampel

	ldi ZH, 0x02					; 0x02 in Higherbyte von Z-Pointer laden

	ldi ZL, 0x00					; 0x00 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10000001			; Hauptstrasse grün, Fussgänger rot
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x01					; 0x01 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10000010			; Hauptstrasse gelb, Fussgänger rot
	st z, Temp						; Ampelbild ablegen	 


	ldi ZL, 0x02					; 0x02 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b01000100			; Hauptstrasse rot, Fussgänger grün
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x03					; 0x03 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10000110			; Hauptstrasse rot-gelb, Fussgänger rot
	st z, Temp						; Ampelbild ablegen

	ret
Ihr seht den Vorteil: Gibt es ein Signalbild zu ändern oder hat sich ein Fehler eingeschlichen, dann muss das nur an einer Stelle korrigiert werden.

Kommen wir damit zur eigentlichen Steuerung, welcher allerdings vermutlich noch vereinfacht werden wird. Das Ganze spielt sich zunächst hauptsächlich in der Serviceroutine des Timers ab und hier frage ich erstmal das Statusbyte ab.

Code: Alles auswählen

	Timer_ISR:						; Interuptserviceroutine des Timers
	sbrs Status, 0					; Ist Bit0 in Register Status gesetzt?
	rjmp Autoampel_aus				; Momentan wird mit S2 die Ampel ausgeschalten
Mit Taster 2 schalte ich also erstmal die Ampel nur aus/ein, wobei "aus" bedeutet, dass Haupt- und Nebenstrasse gelb leuchten und die Fussgängerampel aus ist. Realisiert wird das hier:

Code: Alles auswählen

	Autoampel_aus:
	ldi Temp, 0b00010010			; Haupt- und Nebenstrasse gelb
	out Portb, Temp					; Ausgabe Ampelbild
	
	
	reti							; Ende ISR
Der Beginn der Timer-Routine wird gewiss noch überarbeitet aber erstmal laden wir das Higherbyte des Z-Pointers für die Automatikampel vor:

Code: Alles auswählen

	Automatikampel:
	ldi ZH, 0x01					; Z-Pointer vorladen
Theoretisch könnten wir jetzt immer nach Ablauf eines Zählers das nächste Ampelbild aus der Datenbank holen und ausgeben, währen da nicht die unterschiedlichen Zeiten bei Gelb- und Rot/Grün-Phase. Ich werde also erstmal schauen, ob eine gelb-Phase anliegt und entsprechend dessen eine lange oder kurze Zählschleife aufrufen. Die Zählschleife zählt dabei bei jedem Timeraufruf um 1 hoch und endet jeweils unterschiedlich:

Code: Alles auswählen

sbis portb, 1					; Liegt momentan Gelb-Phase an?
	rjmp Auto_lang
Die beiden Zählschleifen unterscheiden sich nur durch den Endwert, deshalb bespreche ich nur eine davon. Ist das Ende des Zählers erreicht, dann wird zur Ampelsteuerung gesprungen.

Code: Alles auswählen

	Auto_kurz:
	cpi Time1, 0x2F					; Timer abgelaufen?
	brne Auto_kurz_ende				; Wenn nein, dann Kurztimer Ende
	rjmp Automatikampel_s			; Wenn ja, dann zur Ampelsteuerung

	Auto_kurz_ende:
	inc Time1						; Time1 hochzählen
	reti							; Ende ISR
In der Ampelsteuerung setze ich erstmal mein Zählwerk auf 0. Anschliessend hole ich mir das nächste Ampelbild aus der Datenbank und gebe es aus. Ist dieses nächste Ampelbild aus dem Bereich unserer festgelegten Datenbank wird das erste Ampelbild geladen.
Automatikampel_s:
ldi Time1, 0x00 ; Time zurücksetzen
inc ZL ; Z-Pointer um 1 erhöhen
cpi ZL, 0x04 ; Steht ZL auf 04?
brne Automatikampel_s1 ; Wenn nein, dann weiter
ldi ZL, 0x00 ; Wenn ja, ZL=00
Automatikampel_s1:
ld Temp, z ; Inhalt von Z nach Temp laden
out portb, Temp ; Ausgabe neues Ampelbild

reti ; Ende der ISR
Das funktionsfähige Programm sieht damit so aus:

Code: Alles auswählen

; Workshop Projekt Ampel
; Autoren: Teilnehmer des Assemblerworkshops

; Version 2

; Vorgaben:
; EEPROM-Adresse für Statusbyte: 0x00FF



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

.def Time1 = r16					; Zeitvariable 
.def Temp = r20						; temporäre Variable
.def Status = r21					; Statusbyte

; Z-Pointer einrichten

.def ZH = r31
.def ZL = r30

.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 Temp, 0x00               ; alle Bits in Temp auf 0 setzen
	out ddrd, Temp               ; Alle Pins von Port D sind Eingang

	ldi Temp, 0xFF               ; Alle Bits ind Temp auf 1 setzen
	out portd, Temp               ; PC2 und PC3 bekommen PullUp-Widerstände

	; Initialisieren der Ausgänge
	out ddrb, Temp               ; Alle Pins von Port B sind Ausgang (Temp ist noch 0xFF)


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

      

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

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

	; Einlesen des Statusbytes
	
	Lese_EEProm:       				; Unterprogramm zum Auslesen des EEPROM
	sbic EECR, EEWE         		; ist der letzte Schreibvorgang beendet?
	rjmp Lese_EEProm      			; Wenn nein, dann nochmal prüfen

	ldi Temp, 0x00     				; Higher Byte ist 0x00
	out EEARH, Temp 				; Schreibe Higher Byte in Adressregister

	ldi Temp, 0xFF      			; Lower Byte ist 0xFF
	out EEARL, Temp  				; Schreibe Lower Byte in Adressregister

	sbi EECR, EERE  				; Lesevorgang einleiten
	in Status, EEDR   				; Ausgelesenes Byte nach Status schreiben
	andi Status, 0x01				; Bit0 ist Statusbit, alle anderen löschen

	rcall Init_Datenbank			; Initialisierung der Datenbank

	sei                        		; Interrupts erlauben


	Hauptprogramm:					; Hier beginnt das Hauptprogramm

	sbis Pind, 3					; Ist T2 gedrückt
	rcall Taste2					; Wenn ja, dann Taste2 abarbeiten
	rjmp Hauptprogramm				; Hauptprogramm ist Endlosschleife
	
;------------------------------------------------------------------------------	
	
	
	Timer_ISR:						; Interuptserviceroutine des Timers
	sbrs Status, 0					; Ist Bit0 in Register Status gesetzt?
	rjmp Autoampel_aus				; Momentan wird mit S2 die Ampel ausgeschalten


	Automatikampel:
	ldi ZH, 0x01					; Z-Pointer vorladen

	sbis portb, 1					; Liegt momentan Gelb-Phase an?
	rjmp Auto_lang

	Auto_kurz:
	cpi Time1, 0x2F					; Timer abgelaufen?
	brne Auto_kurz_ende				; Wenn nein, dann Kurztimer Ende
	rjmp Automatikampel_s			; Wenn ja, dann zur Ampelsteuerung

	Auto_kurz_ende:
	inc Time1						; Time1 hochzählen
	reti							; Ende ISR


	Auto_lang:
	cpi Time1, 0xFF					; Timer abgelaufen?
	brne Auto_lang_ende				; Wenn nein, dann Kurztimer Ende
	rjmp Automatikampel_s			; Wenn ja, dann zur Ampelsteuerung

	Auto_lang_ende:
	inc Time1						; Time1 hochzählen
	reti							; Ende ISR


	Automatikampel_s:
	ldi Time1, 0x00					; Time zurücksetzen
	inc ZL							; Z-Pointer um 1 erhöhen
	cpi ZL, 0x04					; Steht ZL auf 04?
	brne Automatikampel_s1			; Wenn nein, dann weiter
	ldi ZL, 0x00					; Wenn ja, ZL=00
	Automatikampel_s1:
	ld Temp, z						; Inhalt von Z nach Temp laden
	out portb, Temp					; Ausgabe neues Ampelbild

	reti							; Ende der ISR

	Autoampel_aus:
	ldi Temp, 0b00010010			; Haupt- und Nebenstrasse gelb
	out Portb, Temp					; Ausgabe Ampelbild
	
	
	reti							; Ende ISR


			
	; Ab hier folgen die Unterprogramme

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

	Taste2:							; Abarbeitung Taste2
	sbis Pind, 3					; Taste 2 losgelassen?
	rjmp Taste2						; Wenn nein, nochmal warten

	cpi Status, 0x01				; Ist Bit0 in Statusregister gesetzt?
	brne Taste2_1					; Wenn nein, springe zum Label Taste2_1

	ldi Status, 0x00				; Lösche Bit0 in Statusregister
	rjmp Taste2_Ende				; Springe zum Ende

	Taste2_1:
	ldi Status, 0x01				; Setze Bit0 in Statusregister

	Taste2_Ende:

	ret								; Ende UP



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

	Test1:							; Testroutine
	out PortB, Status				; Ausgabe des Statusbytes
	ret								; Ende UP


;------------------------------------------------------------------------------
Init_Datenbank:
									; Ampelbilder Automatikampel

	ldi ZH, 0x01					; 0x01 in Higherbyte von Z-Pointer laden

	ldi ZL, 0x00					; 0x00 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10100001			; Hauptstrasse grün, Nebenstrasse rot
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x01					; 0x01 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10110010			; Hauptstrasse gelb, Nebenstrasse rot-gelb
	st z, Temp						; Ampelbild ablegen	 


	ldi ZL, 0x02					; 0x02 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b01001100			; Hauptstrasse rot, Nebenstrasse grün
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x03					; 0x03 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10010110			; Hauptstrasse rot-gelb, Nebenstrasse gelb
	st z, Temp						; Ampelbild ablegen


									; Ampelbilder Anforderungsampel

	ldi ZH, 0x02					; 0x02 in Higherbyte von Z-Pointer laden

	ldi ZL, 0x00					; 0x00 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10000001			; Hauptstrasse grün, Fussgänger rot
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x01					; 0x01 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10000010			; Hauptstrasse gelb, Fussgänger rot
	st z, Temp						; Ampelbild ablegen	 


	ldi ZL, 0x02					; 0x02 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b01000100			; Hauptstrasse rot, Fussgänger grün
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x03					; 0x03 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10000110			; Hauptstrasse rot-gelb, Fussgänger rot
	st z, Temp						; Ampelbild ablegen

	ret
Kurze Funktionsbeschreibung des bisher Erarbeiteten, in Klammern ein paar Fleissaufgaben ;)
- Nach dem Einschalten sind alle LEDs eine Rotphase lang aus (könnte man ändern, wie?)
- LED1-3 stehen für die Ampel der Hauptstrasse, LED4-6 für die Nebenstrasse und 7-8 für eine Fussgängerampel über die Hauptstrasse
- Die Ampel wird über feste Zeiten gesteuert (Wo könnte man diese momentan anpassen?)
- Mit T2 wird die Ampel aus/eingeschalten. Aus bedeutet Gelblicht auf den Strassen und Aus für die Fussgängerampel

Im Prinzip kann man dieses Programm bereits in der Praxis gebrauchen. Viel Spass damit, in ein oder zwei Wochen geht es weiter. Fragen und Anregungen... her damit ;)

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#122

Beitrag von Muenchner Kindl »

Hallo nochmal,

es gibt eine kleine Änderung, vorbereitend auf künftige Funktionen. Zum Einen war es erforderlich, eine weitere Zählschleife einzubauen, zum anderen mussten bei der Abarbeitung des Tasters ein paar Dinge berücksichtigt werden.

Für die weitere Zählschleife gibt es eine neue Variable:

Code: Alles auswählen

.def Time2 = r17					; zweite Zeitvariable
Beim Initialisieren werden auch die Zähler berücksichtigt:

Code: Alles auswählen

	; Initialisieren der Zähler
	ldi Time1, 0x00					; Time1 beginnt bei 00
	ldi Time2, 0x0F					; Time2 beginnt bei 0x0F
Ebenfalls beim Initialisieren werden die Strassen auf gelb geschalten (ist besser als alles aus):

Code: Alles auswählen

	ldi Temp, 0b00010010           ; Fussgänger aus, Strassen gelb
	out portb, Temp 


Bei den beiden Zählwerken kommt jeweils eine übergeordnete Zählschleife dazu:

Code: Alles auswählen

dec Time2						; Time2 um 1 zurückzählen
	cpi Time2, 0x00					; Ist Time2 abgelaufen?
	breq Auto_kurz_1				; Wenn ja, dann nächste Zählschleife
	reti							; Wenn nein, Ende ISR

	Auto_kurz_1:
	ldi Time2, 0x0F					; Time2 wieder vorladen
Während der Abarbeitung von T2 werden erstens alle Interrupts gesperrt, zweitens werden die Zähler neu initialisiert:

Code: Alles auswählen

	Taste2:						; Abarbeitung Taste2
	sbis Pind, 3					; Taste 2 losgelassen?
	rjmp Taste2						; Wenn nein, nochmal warten
	cli								; Interrupts sperren
	ldi Time2, 0x0F					; Time2 neu initialisieren
	ldi Time1, 0x00					; Time1 neu initialisieren

	cpi Status, 0x01				; Ist Bit0 in Statusregister gesetzt?
	brne Taste2_1					; Wenn nein, springe zum Label Taste2_1

	ldi Status, 0x00				; Lösche Bit0 in Statusregister
	rjmp Taste2_Ende				; Springe zum Ende

	Taste2_1:
	ldi Status, 0x01				; Setze Bit0 in Statusregister

	Taste2_Ende:
	sei								; Interrupts erlauben
	ret								; Ende UP
Warum die Änderungen, vor allem warum die zweite Zählschleife?
Das Problem war, dass die einstellige Zählschleife bereits ausgeschöpft war. Eine längere Rotphase war nicht möglich, was eigentlich nicht wirklich sinnvoll ist. Deshalb eine weitere Zählschleife, die quasi die Mindestzeit einer Ampelphase beschreibt.
Die Änderungen bei der Taster-Routine war notwendig, um ein Verändern der Zähler während der Abarbeitung zu unterbinden. Spätestens wenn wir den Status auf das EEPROM schreiben wäre das eh nötig gewesen.

Und damit zum kompletten Programm, eine automatische Verkehrsampel mit Fussgängerampel und der vorübergehenden Möglichkeit, diese mit S2 abzuschalten:

Code: Alles auswählen

; Workshop Projekt Ampel
; Autoren: Teilnehmer des Assemblerworkshops

; Version 2

; Vorgaben:
; EEPROM-Adresse für Statusbyte: 0x00FF



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

.def Time1 = r16					; Zeitvariable 
.def Time2 = r17					; zweite Zeitvariable
.def Temp = r20						; temporäre Variable
.def Status = r21					; Statusbyte

; Z-Pointer einrichten

.def ZH = r31
.def ZL = r30

.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 Temp, 0x00               ; alle Bits in Temp auf 0 setzen
	out ddrd, Temp               ; Alle Pins von Port D sind Eingang

	ldi Temp, 0xFF               ; Alle Bits ind Temp auf 1 setzen
	out portd, Temp               ; PC2 und PC3 bekommen PullUp-Widerstände

	; Initialisieren der Ausgänge
	out ddrb, Temp               ; Alle Pins von Port B sind Ausgang (Temp ist noch 0xFF)


	ldi Temp, 0b00010010           ; Fussgänger aus, Strassen gelb
	out portb, Temp               

      

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

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

	; Einlesen des Statusbytes
	
	Lese_EEProm:       				; Unterprogramm zum Auslesen des EEPROM
	sbic EECR, EEWE         		; ist der letzte Schreibvorgang beendet?
	rjmp Lese_EEProm      			; Wenn nein, dann nochmal prüfen

	ldi Temp, 0x00     				; Higher Byte ist 0x00
	out EEARH, Temp 				; Schreibe Higher Byte in Adressregister

	ldi Temp, 0xFF      			; Lower Byte ist 0xFF
	out EEARL, Temp  				; Schreibe Lower Byte in Adressregister

	sbi EECR, EERE  				; Lesevorgang einleiten
	in Status, EEDR   				; Ausgelesenes Byte nach Status schreiben
	andi Status, 0x01				; Bit0 ist Statusbit, alle anderen löschen

	rcall Init_Datenbank			; Initialisierung der Datenbank
	; Initialisieren der Zähler
	ldi Time1, 0x00					; Time1 beginnt bei 00
	ldi Time2, 0x0F					; Time2 beginnt bei 0x0F

	sei                        		; Interrupts erlauben


	Hauptprogramm:					; Hier beginnt das Hauptprogramm

	sbis Pind, 3					; Ist T2 gedrückt
	rcall Taste2					; Wenn ja, dann Taste2 abarbeiten
	rjmp Hauptprogramm				; Hauptprogramm ist Endlosschleife
	
;------------------------------------------------------------------------------	
	
	
	Timer_ISR:						; Interuptserviceroutine des Timers
	sbrs Status, 0					; Ist Bit0 in Register Status gesetzt?
	rjmp Autoampel_aus				; Momentan wird mit S2 die Ampel ausgeschalten


	Automatikampel:
	ldi ZH, 0x01					; Z-Pointer vorladen

	sbis portb, 1					; Liegt momentan Gelb-Phase an?
	rjmp Auto_lang

	Auto_kurz:
	dec Time2						; Time2 um 1 zurückzählen
	cpi Time2, 0x00					; Ist Time2 abgelaufen?
	breq Auto_kurz_1				; Wenn ja, dann nächste Zählschleife
	reti							; Wenn nein, Ende ISR

	Auto_kurz_1:
	ldi Time2, 0x0F					; Time2 wieder vorladen
	cpi Time1, 0x01					; Time1 abgelaufen?
	brne Auto_kurz_ende				; Wenn nein, dann Kurztimer Ende
	rjmp Automatikampel_s			; Wenn ja, dann zur Ampelsteuerung

	Auto_kurz_ende:
	inc Time1						; Time1 hochzählen
	reti							; Ende ISR


	Auto_lang:
	dec Time2						; Time2 um 1 zurückzählen
	cpi Time2, 0x00					; Ist Time2 abgelaufen?
	breq Auto_lang_1				; Wenn ja, dann nächste Zählschleife
	reti							; Wenn nein, Ende ISR
	
	Auto_lang_1:
	ldi Time2, 0x0F					; Time2 wieder vorladen
	cpi Time1, 0x1F					; Timer abgelaufen?
	brne Auto_lang_ende				; Wenn nein, dann Kurztimer Ende
	rjmp Automatikampel_s			; Wenn ja, dann zur Ampelsteuerung

	Auto_lang_ende:
	inc Time1						; Time1 hochzählen
	reti							; Ende ISR


	Automatikampel_s:
	ldi Time1, 0x00					; Time zurücksetzen
	inc ZL							; Z-Pointer um 1 erhöhen
	cpi ZL, 0x04					; Steht ZL auf 04?
	brne Automatikampel_s1			; Wenn nein, dann weiter
	ldi ZL, 0x00					; Wenn ja, ZL=00
	Automatikampel_s1:
	ld Temp, z						; Inhalt von Z nach Temp laden
	out portb, Temp					; Ausgabe neues Ampelbild

	reti							; Ende der ISR

	Autoampel_aus:
	ldi Temp, 0b00010010			; Haupt- und Nebenstrasse gelb
	out Portb, Temp					; Ausgabe Ampelbild
	
	
	reti							; Ende ISR


			
	; Ab hier folgen die Unterprogramme

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

	Taste2:						; Abarbeitung Taste2
	sbis Pind, 3					; Taste 2 losgelassen?
	rjmp Taste2						; Wenn nein, nochmal warten
	cli								; Interrupts sperren
	ldi Time2, 0x0F					; Time2 neu initialisieren
	ldi Time1, 0x00					; Time1 neu initialisieren

	cpi Status, 0x01				; Ist Bit0 in Statusregister gesetzt?
	brne Taste2_1					; Wenn nein, springe zum Label Taste2_1

	ldi Status, 0x00				; Lösche Bit0 in Statusregister
	rjmp Taste2_Ende				; Springe zum Ende

	Taste2_1:
	ldi Status, 0x01				; Setze Bit0 in Statusregister

	Taste2_Ende:
	sei								; Interrupts erlauben
	ret								; Ende UP



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

	Test1:							; Testroutine
	out PortB, Status				; Ausgabe des Statusbytes
	ret								; Ende UP


;------------------------------------------------------------------------------
Init_Datenbank:
									; Ampelbilder Automatikampel

	ldi ZH, 0x01					; 0x01 in Higherbyte von Z-Pointer laden

	ldi ZL, 0x00					; 0x00 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10100001			; Hauptstrasse grün, Nebenstrasse rot
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x01					; 0x01 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10110010			; Hauptstrasse gelb, Nebenstrasse rot-gelb
	st z, Temp						; Ampelbild ablegen	 


	ldi ZL, 0x02					; 0x02 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b01001100			; Hauptstrasse rot, Nebenstrasse grün
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x03					; 0x03 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10010110			; Hauptstrasse rot-gelb, Nebenstrasse gelb
	st z, Temp						; Ampelbild ablegen


									; Ampelbilder Anforderungsampel

	ldi ZH, 0x02					; 0x02 in Higherbyte von Z-Pointer laden

	ldi ZL, 0x00					; 0x00 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10000001			; Hauptstrasse grün, Fussgänger rot
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x01					; 0x01 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10000010			; Hauptstrasse gelb, Fussgänger rot
	st z, Temp						; Ampelbild ablegen	 


	ldi ZL, 0x02					; 0x02 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b01000100			; Hauptstrasse rot, Fussgänger grün
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x03					; 0x03 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10000110			; Hauptstrasse rot-gelb, Fussgänger rot
	st z, Temp						; Ampelbild ablegen

	ret
Evtl. Änderungen demnächst:
Evtl. können wir die Statusvariable weglassen. Dann würden wir das Higherbyte des Z-Pointers als Status verwenden

Kleiner Hinweis: Es gibt noch keine fertige Version des Programms. Es wird quasi hier Schritt für Schritt entwickelt, mit all seinen Änderungen, Ideen und falschen Ansätzen.

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#123

Beitrag von Muenchner Kindl »

Hallo ein letztes Mal für heute...

Ich habe das Statusbyte weggelassen und verwende stattdessen das Higherbyte des Z-Pointers. Dazu sind ein paar Änderungen notwendig:

Nach dem Auslesen des EEPROM müssen wir sehen, ob da ein gültiger Wert drinsteht:

Code: Alles auswählen

	in ZH, EEDR 	  				; Ausgelesenes Byte in Z-Pointer übernehmen
	cpi ZH, 0x03					; Ist der Wert größer als 0x02?
	brge Korrektur_ZH				; wenn ja, springe zur Korrektur
	cpi ZH, 0x00					; Ist der Wert 0?
	breq Korrektur_ZH				; wenn ja, springe zur Korrektur
	rjmp Init_Ende

	Korrektur_ZH:
	ldi ZH, 0x01


	init_Ende:
Unser Taster T2 verändert ab jetzt das ZH-Register:

Code: Alles auswählen

	Taste2:						; Abarbeitung Taste2
	sbis Pind, 3					; Taste 2 losgelassen?
	rjmp Taste2						; Wenn nein, nochmal warten
	cli								; Interrupts sperren
	ldi Time2, 0x0F					; Time2 neu initialisieren
	ldi Time1, 0x00					; Time1 neu initialisieren

	cpi ZH, 0x01				; Ist Bit0 in Statusregister gesetzt?
	brne Taste2_1					; Wenn nein, springe zum Label Taste2_1

	ldi ZH, 0x02				; Lösche Bit0 in Statusregister
	rjmp Taste2_Ende				; Springe zum Ende

	Taste2_1:
	ldi ZH, 0x01				; Setze Bit0 in Statusregister

	Taste2_Ende:
	sei								; Interrupts erlauben
	ret								; Ende UP
In unserer Timer-Routine fehlen ein paar Zeilen ;)

Code: Alles auswählen

	Timer_ISR:						; Interuptserviceroutine des Timers

	sbis portb, 1					; Liegt momentan Gelb-Phase an?
	rjmp Auto_lang
Damit ist es jetzt so, dass man bereits mit S2 den Modus grob umschaltet. Entweder Verkehrsampel mit Fussgängerampel oder nur Fussgängerampel, noch ohne Aufforderung.

Die Aufforderungsfunktion machen wir nächste Woche, hier das komplette Programm in der Version 3:

Code: Alles auswählen

; Workshop Projekt Ampel
; Autoren: Teilnehmer des Assemblerworkshops

; Version 3

; Vorgaben:
; EEPROM-Adresse für Statusbyte: 0x00FF



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

.def Time1 = r16					; Zeitvariable 
.def Time2 = r17					; zweite Zeitvariable
.def Temp = r20						; temporäre Variable
;.def Status = r21					; Statusbyte

; Z-Pointer einrichten

.def ZH = r31
.def ZL = r30

.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 Temp, 0x00               ; alle Bits in Temp auf 0 setzen
	out ddrd, Temp               ; Alle Pins von Port D sind Eingang

	ldi Temp, 0xFF               ; Alle Bits ind Temp auf 1 setzen
	out portd, Temp               ; PC2 und PC3 bekommen PullUp-Widerstände

	; Initialisieren der Ausgänge
	out ddrb, Temp               ; Alle Pins von Port B sind Ausgang (Temp ist noch 0xFF)


	ldi Temp, 0b00010010           ; Fussgänger aus, Strassen gelb
	out portb, Temp               

      

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

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

	; Einlesen des Statusbytes
	
	Lese_EEProm:       				; Unterprogramm zum Auslesen des EEPROM
	sbic EECR, EEWE         		; ist der letzte Schreibvorgang beendet?
	rjmp Lese_EEProm      			; Wenn nein, dann nochmal prüfen

	ldi Temp, 0x00     				; Higher Byte ist 0x00
	out EEARH, Temp 				; Schreibe Higher Byte in Adressregister

	ldi Temp, 0xFF      			; Lower Byte ist 0xFF
	out EEARL, Temp  				; Schreibe Lower Byte in Adressregister

	sbi EECR, EERE  				; Lesevorgang einleiten
	
	
	in ZH, EEDR 	  				; Ausgelesenes Byte in Z-Pointer übernehmen
	cpi ZH, 0x03					; Ist der Wert größer als 0x02?
	brge Korrektur_ZH				; wenn ja, springe zur Korrektur
	cpi ZH, 0x00					; Ist der Wert 0?
	breq Korrektur_ZH				; wenn ja, springe zur Korrektur
	rjmp Init_Ende

	Korrektur_ZH:
	ldi ZH, 0x01


	init_Ende:	
	rcall Init_Datenbank			; Initialisierung der Datenbank
	; Initialisieren der Zähler
	ldi Time1, 0x00					; Time1 beginnt bei 00
	ldi Time2, 0x0F					; Time2 beginnt bei 0x0F

	sei                        		; Interrupts erlauben


	Hauptprogramm:					; Hier beginnt das Hauptprogramm

	sbis Pind, 3					; Ist T2 gedrückt
	rcall Taste2					; Wenn ja, dann Taste2 abarbeiten
	rjmp Hauptprogramm				; Hauptprogramm ist Endlosschleife
	
;------------------------------------------------------------------------------	
	
	
	Timer_ISR:						; Interuptserviceroutine des Timers

	sbis portb, 1					; Liegt momentan Gelb-Phase an?
	rjmp Auto_lang

	Auto_kurz:
	dec Time2						; Time2 um 1 zurückzählen
	cpi Time2, 0x00					; Ist Time2 abgelaufen?
	breq Auto_kurz_1				; Wenn ja, dann nächste Zählschleife
	reti							; Wenn nein, Ende ISR

	Auto_kurz_1:
	ldi Time2, 0x0F					; Time2 wieder vorladen
	cpi Time1, 0x01					; Time1 abgelaufen?
	brne Auto_kurz_ende				; Wenn nein, dann Kurztimer Ende
	rjmp Automatikampel_s			; Wenn ja, dann zur Ampelsteuerung

	Auto_kurz_ende:
	inc Time1						; Time1 hochzählen
	reti							; Ende ISR


	Auto_lang:
	dec Time2						; Time2 um 1 zurückzählen
	cpi Time2, 0x00					; Ist Time2 abgelaufen?
	breq Auto_lang_1				; Wenn ja, dann nächste Zählschleife
	reti							; Wenn nein, Ende ISR
	
	Auto_lang_1:
	ldi Time2, 0x0F					; Time2 wieder vorladen
	cpi Time1, 0x05					; Timer abgelaufen?
	brne Auto_lang_ende				; Wenn nein, dann Kurztimer Ende
	rjmp Automatikampel_s			; Wenn ja, dann zur Ampelsteuerung

	Auto_lang_ende:
	inc Time1						; Time1 hochzählen
	reti							; Ende ISR


	Automatikampel_s:
	ldi Time1, 0x00					; Time zurücksetzen
	inc ZL							; Z-Pointer um 1 erhöhen
	cpi ZL, 0x04					; Steht ZL auf 04?
	brne Automatikampel_s1			; Wenn nein, dann weiter
	ldi ZL, 0x00					; Wenn ja, ZL=00
	Automatikampel_s1:
	ld Temp, z						; Inhalt von Z nach Temp laden
	out portb, Temp					; Ausgabe neues Ampelbild

	reti							; Ende der ISR

	Autoampel_aus:
	ldi Temp, 0b00010010			; Haupt- und Nebenstrasse gelb
	out Portb, Temp					; Ausgabe Ampelbild
	
	
	reti							; Ende ISR


			
	; Ab hier folgen die Unterprogramme

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

	Taste2:						; Abarbeitung Taste2
	sbis Pind, 3					; Taste 2 losgelassen?
	rjmp Taste2						; Wenn nein, nochmal warten
	cli								; Interrupts sperren
	ldi Time2, 0x0F					; Time2 neu initialisieren
	ldi Time1, 0x00					; Time1 neu initialisieren

	cpi ZH, 0x01				; Ist Bit0 in Statusregister gesetzt?
	brne Taste2_1					; Wenn nein, springe zum Label Taste2_1

	ldi ZH, 0x02				; Lösche Bit0 in Statusregister
	rjmp Taste2_Ende				; Springe zum Ende

	Taste2_1:
	ldi ZH, 0x01				; Setze Bit0 in Statusregister

	Taste2_Ende:
	sei								; Interrupts erlauben
	ret								; Ende UP



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



;------------------------------------------------------------------------------
Init_Datenbank:
									; Ampelbilder Automatikampel

	ldi ZH, 0x01					; 0x01 in Higherbyte von Z-Pointer laden

	ldi ZL, 0x00					; 0x00 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10100001			; Hauptstrasse grün, Nebenstrasse rot
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x01					; 0x01 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10110010			; Hauptstrasse gelb, Nebenstrasse rot-gelb
	st z, Temp						; Ampelbild ablegen	 


	ldi ZL, 0x02					; 0x02 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b01001100			; Hauptstrasse rot, Nebenstrasse grün
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x03					; 0x03 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10010110			; Hauptstrasse rot-gelb, Nebenstrasse gelb
	st z, Temp						; Ampelbild ablegen


									; Ampelbilder Anforderungsampel

	ldi ZH, 0x02					; 0x02 in Higherbyte von Z-Pointer laden

	ldi ZL, 0x00					; 0x00 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10000001			; Hauptstrasse grün, Fussgänger rot
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x01					; 0x01 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10000010			; Hauptstrasse gelb, Fussgänger rot
	st z, Temp						; Ampelbild ablegen	 


	ldi ZL, 0x02					; 0x02 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b01000100			; Hauptstrasse rot, Fussgänger grün
	st z, Temp						; Ampelbild ablegen


	ldi ZL, 0x03					; 0x03 in Lowerbyte von Z-Pointer laden
	ldi Temp, 0b10000110			; Hauptstrasse rot-gelb, Fussgänger rot
	st z, Temp						; Ampelbild ablegen

	ret

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

Re: Workshop: Programmierung in Assembler (Atmel ATM8)

#124

Beitrag von Muenchner Kindl »

Guten Morgen,

nun ist hier schon fast ein Jahr Stillstand und es hat mich nicht eine Beschwerde erreicht ;-)

Ich könnte mir natürlich jetzt eine ganze Reihe toller Ausreden einfallen lassen, aber ich bin ehrlich... von meiner Seite her war es ein Mix aus vieler Arbeit im Job, Familiärer Herausforderung und eine Prise Faulheit/Bequemlichkeit, die mich von diesem Workshop ferngehalten haben.

Jetzt ist es ja so, daß der eigentliche Stoff ja durch ist und das Ganze erst während des "Gesellenstücks" ins Stocken geraten ist. Deshalb meine Frage an Euch, sollen wir das gemeinsam fertig machen, hat das vielleicht jemand in Eigenregie fertig gemacht, gibt es evtl. andere Wünsche oder könnt Ihr mit dem, was wir bisher gemacht haben leben?
Wir könnten auch gemeinsame Projekte in die Welt setzen, wer Lust hat, einfach mal melden.

Kleiner Hinweis am Rande: Es gibt bereits das Atmel Studio in der Version 6, da bin ich jetzt erstmal am Testen. Schaut ziemlich mächtig aus, braucht aber auch immensen Speicher auf Festplatte und RAM.

Ich werde nun in den kommenden Tagen den Workshop, selbstverständlich nur meine eigenen Texte, auch auf meiner kommerziellen Seite veröffentlichen.

Viele Grüße,

Thomas
Antworten

Zurück zu „Workshops“