Archiv verlassen und diese Seite im Standarddesign anzeigen : Anfänger mit STK500 und Assembler
robo_wolf
21.12.2009, 11:57
Hallo Leute,
beschäftige mich mit der Programmierung des STK500 mit Assembler.
Naja vielleicht sollte ich lieber schreiben, ein Newbi möchte sich mit der Assemblerprogrammierung des STK500 beschäftigen.
Als Software benutze ich die mitgelieferte AVR-Studio.
Habe im Vorfeld schon einiges in Foren und Büchern gelesen.
So als Einstieg und zum besseren Erlernen möchte ich mir kleine Aufgaben stellen und diese dann umsetzen.
Den Code werde ich hier posten und wäre sehr an euren Meinungen, Vorschlägen und Kritiken interessiert.
Zu den Code werde ich immer Kommentare anfügen, damit man versteht was ich damit bezwecke.
Anfangen möchte ich mit einer einfachen Tastenschaltung.
Bei dieser sollen die 8 LEDs des STK500 mit einer Taste entprellt an- bzw. ausgeschaltet werden.
Das STK500 habe ich mit einen ATMega8515 bestückt. PORTB ist mit den Tastern und PORTD mit den LEDs verbunden.
;***** STK500 Lernprogramm
;*** Aufgabe: alle LEDs mit einem Taster auf dem STK500 an bzw. ausschalten
;*** zum Entprellen soll ein Interrupt(Int0) benutzt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary register
.def LED_STAT = r17 ; LED sind mit PortB verbunden
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
rjmp INT0_ISR ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
RESET:
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
ldi temp, 1 << INT0 ;Interrupt INT0 aktiviert
out GICR, temp
ldi temp, 1 << ISC00 ;Interrupt INT0 konfiguriert
ori temp, 1 << ISC01
out MCUCR, temp
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out DDRD, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out PORTD, temp ;PullUp an PortD einschalten
out DDRB,Temp ;PORTB als Ausgang
out PORTB, temp ;PORTB (LEDs) aus
sei ;Interrupts zulassen
MAIN:
rjmp MAIN ;Die Schleife ruft mit dem Sprungbefehl
;rjmp sich ständig selbst auf.(endlos)
INT0_ISR:
cli ;Interrupts sperren
in LED_STAT, PORTB ;PORTB auslesen in LED_STAT
com LED_STAT ;Invertieren von LED_STAT
out PORTB, LED_STAT ;Ausgabe von temp an PORTB -> LEDs aus
reti
Das Programm habe ich zum STK500 übertragen und tut auch das was es soll.
mare_crisium
21.12.2009, 13:43
robo_wolf,
da hast Du Dir einen sehr guten und erfolgsversprechenden Zugang ausgesucht :-) ! Ein paar Anmerkungen zu Deinem Programm:
Du musst bedenken, dass die Interrupt-Dienstprozedur INT0_ISR dieselben Register verwendet, wie das Hauptprogramm. Wenn also die Haupschleife MAIN mit bestimmten Registern etwas ausrechnen wollte, als der Interrupt sie unterbrach, dann erwartet sie, dass die Register bei der Rückkehr aus dem Interrupt wieder dieselben Werte haben wie vorher. Du musst also dafür sorgen, dass die Registerinhalte gleich zu Beginn von INT0_ISR auf den Stack gesichert (push rxx) und unmittelbar vor der Rückkehr (reti) wiederhergestellt (pop rxx) werden. Wichtig ist auch, den aktuellen Inhalt des SREG auf dem Stack zu sichern. Sonst gehen dem Hauptprogramm so wichtige Sachen wie der aktuelle Stand des Carry- oder Zero-Flags verloren. Also brauchst Du parallel zum Sichern der Register noch die Zeilen
Aus leidvoller (:-() Erfahrung habe ich mir angewöhnt, vor jedem Unterprogramm und jedem Interruptdienst aufzuschreiben, welche Register wie verwendet werden. Z.B. so:
/*
Der Steuerblock und die Daten der FIFO werden im RAM angelegt.
Die Offsets der Steuerdaten sind
+0 : FIFO_FLAGS
+1 : FIFO_WR zeigt auf nächste Schreibposition
+2 : FIFO_RD zeigt auf nächste Leseposition
+3 : FIFO_CAP FIFO-Kapazität, maximal 255 Byte
Datenblock:
+4 : <1. Datenbyte>
Die Bedeutung der Bits in FIFO_FLAGS ist:
XXXX XXXX
||
|+------ 0: FIFO ist nicht voll
| 1: FIFO ist voll
|
+------- 0: FIFO ist nicht leer
1: FIFO ist leer
*/
;------------------------------------------
;
; FIFO-KONSTANTEN
;
; BYTE-OFFSETs
.equ FIFO_FLAGS = 0
.equ FIFO_WR = 1 ; zeigt auf nächste freie Position
.equ FIFO_RD = 2 ; zeigt auf nächste belegte Position
.equ FIFO_CAP = 3 ;
.equ FIFO_LNG = 4 ; Länge des Datenblocks
;BIT-OFFSETs
.equ FIFO_VOLL = 0
.equ FIFO_LEER = 1 ;
; Der Platzbedarf im RAM
; berechnet sich als
; FIFO_SIZE = FIFO_LNG + FIFO_CAP
/*------------------------------------------
PROCEDURE FIFO8_CREATE
erzeugt die Struktur einer leeren FIFO für 1-Byte-Daten im RAM
Eingangsvariablen
zh:zl: enhält den Zeiger auf die RAM-Adresse,
an der die FIFO angelegt werden soll
r16: enthält die FIFO-Kapazität
Ausgangsvariablen
zh:zl : enthält den Zeiger auf die FIFO
r16: enthält die FIFO-Kapazität
geänderte Register
keine
geänderte Ports
keine
*/
FIFO8_CREATE:
push r17
in r17,SREG
push r17
push r18
; FIFO-Steuerblock mit Nullen füllen
ldi r18,FIFO_LNG
clr r17
FIFO_CREAT01:
tst r18
breq FIFO_CREAT_00
st z+,r17
dec r18
rjmp FIFO_CREAT01
FIFO_CREAT_00:
; zh:zl wieder auf Anfang der FIFO stellen
sbiw zh:zl,FIFO_LNG
; Anfangszustand der Flags einstellen
ldi r17,(1<<FIFO_LEER); Flag "FIFO_LEER" setzen
std z+FIFO_FLAGS,r17
dec r16 ;
std z+FIFO_CAP,r16 ; FIFO-Kapazität speichern
inc r16 ;
pop r18
pop r17
out SREG,r17
pop r17
ret
Weiterhin viel Spass und viel Erfolg!
mare_crisium
robo_wolf
21.12.2009, 17:40
Hallo mare_crisium,
habe mein Listing angepasst.
Beim Testen im Simulator habe ich mir den Datenspeicher mit als Fenter anzeigen lassen und den Verlauf verglichen.
Das erste was nach Eintreten der ISR auf den Stack kommt ist der Programmcounter(zaehler). Dann werden die Registerinhalte, wie ich sie sichere, nach unten gestapelt und spaeter wieder ausgelesen.
Die Speicherinhalte vom Datenspeicher bleiben bestehen, bis sie von erneuten Aufrufen der ISR ggf. ueberschrieben werden.
;***** STK500 Lernprogramm
;*** Aufgabe: alle LEDs mit einem Taster auf dem STK500 an bzw. ausschalten
;*** zum Entprellen soll ein Interrupt(Int0) benutzt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary register
.def LED_STAT = r17 ; LED sind mit PortB verbunden
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
rjmp INT0_ISR ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
RESET:
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
ldi temp, 1 << INT0 ;Interrupt INT0 aktiviert
out GICR, temp
ldi temp, 1 << ISC00 ;Interrupt INT0 konfiguriert
ori temp, 1 << ISC01
out MCUCR, temp
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out DDRD, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out PORTD, temp ;PullUp an PortD einschalten
out DDRB,Temp ;PORTB als Ausgang
out PORTB, temp ;PORTB (LEDs) aus
sei ;Interrupts zulassen
MAIN:
rjmp MAIN ;Die Schleife ruft mit dem Sprungbefehl
;rjmp sich ständig selbst auf.(endlos)
INT0_ISR:
push R16 ;Inhalt von R16 auf Stack ablegen
in R16, SREG ;Statusregister in R16 lesen
push R16 ;Inhalt von R16(SREG) auf den Stack ablegen
push R17 ;Inhalt von R17 auf den Stack ablegen
cli ;Interrupts sperren
in LED_STAT, PORTB ;PORTB auslesen in LED_STAT
com LED_STAT ;Invertieren von LED_STAT
out PORTB, LED_STAT ;Ausgabe von LED_STAT an PORTB -> LEDs an bzw. aus
pop R17 ;Ruecksichern von R17
pop R16 ;Ruecksichern von R16(SREG)
out SREG, R16 ;Ruecksichern von SREG
pop R16
reti
Besserwessi
21.12.2009, 18:27
So wie gezeigt ist das retten der Register schon richtig. In diesem Speziellen Fall, wo im Hauptprogramm nichts wirkliches passiert, kann man auch außnahmsweise mal auf das sichern des SREG verzichten.
Das erste Programm war also schon OK, aber man sollte sich das sichern der Register ruhig angewöhnen.
In ASM ist es ein gar nicht so seltener Trick einfach ein paar Register für die Benutzung im Interrupt zu reservieren, dass spart einem das sichern der Register. Oft nutzt man ja ASM gerade deshalb man das maximale an Geschwindigkeit haben will.
Es ist sonst recht hilfreich wenn man oben als Kommentar reinschreibt zu welcher Hardware das Programm gehört. Also wo hängen Taster oder LEDs dran. Für So einen kleinen Test gehts noch ohne, später sollte das aber dazu.
Ist nur eine Kleinigkeit, aber beim konfigurieren des Interrupts sollte man erst die Einstellungen (Flanke etc.) machen, dann ggf. das Interruptflag löschen, und erst dann den Interrupt einschalten. Sonst gibt es auch mal einen ungeplanten Interrupt vom Konfigurieren.
robo_wolf
21.12.2009, 22:56
Hallo Besserwessi,
erstmal vielen Dank fuer die Tipps...
Habe nun den Code wie folgt abgeaendert.
;***** STK500 Lernprogramm
;*** Aufgabe: alle LEDs mit einem Taster auf dem STK500 an bzw. ausschalten
;*** zum Entprellen soll ein Interrupt(Int0) benutzt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary register
.def LED_STAT = r17 ; LED sind mit PortB verbunden
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
rjmp INT0_ISR ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
RESET:
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
ldi temp, 1 << ISC00 ;Interrupt INT0 konfiguriert
ori temp, 1 << ISC01
out MCUCR, temp
ldi temp, 0 << INTF0 ;InterruptFlagregister geloescht
out GIFR, temp
ldi temp, 1 << INT0 ;Interrupt INT0 aktiviert
out GICR, temp
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out DDRD, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out PORTD, temp ;PullUp an PortD einschalten
out DDRB,Temp ;PORTB als Ausgang
out PORTB, temp ;PORTB (LEDs) aus
sei ;Interrupts zulassen
MAIN:
rjmp MAIN ;Die Schleife ruft mit dem Sprungbefehl
;rjmp sich ständig selbst auf.(endlos)
INT0_ISR:
push R16 ;Inhalt von R16 auf Stack ablegen
in R16, SREG ;Statusregister in R16 lesen
push R16 ;Inhalt von R16(SREG) auf den Stack ablegen
push R17 ;Inhalt von R17 auf den Stack ablegen
in LED_STAT, PORTB ;PORTB auslesen in LED_STAT
com LED_STAT ;Invertieren von LED_STAT
out PORTB, LED_STAT ;Ausgabe von LED_STAT an PORTB -> LEDs an bzw. aus
pop R17 ;Ruecksichern von R17
pop R16 ;Ruecksichern von R16(SREG)
out SREG, R16 ;Ruecksichern von SREG
pop R16
reti
Reihenfolge Konfiguration und Aktivierung des INT0, sowie das Loeschen des INTF0 im GIFR.
Kannst Du mir naeheres zu den Flags sagen?
Ach ja das CLI in der ISR habe ich entfernt, da es automatisch geschieht.
werde ueber Weihnachten nicht zu Haus sein und somit auch nicht antworten koennen.
Besserwessi
22.12.2009, 00:21
Die Reihenfolge bei der Konfiguration des Interrupts ist schon OK.
Allerdings haben die Register mit den Interruptflags eine komische Eigenschaft. Um da ein Bit zu löschen muß man eine 1 reinschreiben.
Das klingt erstmal widersinnig, erlaubt aber so ein direktes und atomares löschen einzelner Bits. Außerden kann man so oft den gleichen Wert zulöschen der Flags reinschreiben. Die Bits haben nämlich sinnigerweise die gleiche Reihenfolge.
kurz gesagt: 0 << INTF0 ist falsch.
Selbst wenn man ein normales Bit löschen wollte, ist das eine umständliche Schreibweise für 0. Man löscht also normal alle Bits, nur nicht grade bei den Interuptflags.
Die beiden Bits für die Interrupt-konfiguration kann man übrigens auch gleich zusammenfügen, das muß man nicht erst zur Laufzeit machen.
Also statt:
ldi temp, 1 << ISC00 ;Interrupt INT0 konfiguriert
ori temp, 1 << ISC01
kürzer
ldi temp, (1 << ISC00) |(1 << ISC01) ;Interrupt INT0 konfiguriert
robo_wolf
22.12.2009, 09:02
Hallo Besserwessi,
bist ja noch spät bzw. schon frueh unterwegs.
Hier wieder eine angepasste Version des Codes:
;***** STK500 Lernprogramm
;*** Aufgabe: alle LEDs mit einem Taster auf dem STK500 an bzw. ausschalten
;*** zum Entprellen soll ein Interrupt(Int0) benutzt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary register
.def LED_STAT = r17 ; LED sind mit PortB verbunden
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
rjmp INT0_ISR ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
RESET:
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
ldi temp, (1<<ISC00)|(1<<ISC01) ;Interrupt INT0 konfiguriert
out MCUCR, temp
ldi temp, 1 << INTF0 ;InterruptFlagregister geloescht
out GIFR, temp
ldi temp, 1 << INT0 ;Interrupt INT0 aktiviert
out GICR, temp
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out DDRD, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out PORTD, temp ;PullUp an PortD einschalten
out DDRB,Temp ;PORTB als Ausgang
out PORTB, temp ;PORTB (LEDs) aus
sei ;Interrupts zulassen
MAIN:
rjmp MAIN ;Die Schleife ruft mit dem Sprungbefehl
;rjmp sich ständig selbst auf.(endlos)
INT0_ISR:
push R16 ;Inhalt von R16 auf Stack ablegen
in R16, SREG ;Statusregister in R16 lesen
push R16 ;Inhalt von R16(SREG) auf den Stack ablegen
push R17 ;Inhalt von R17 auf den Stack ablegen
in LED_STAT, PORTB ;PORTB auslesen in LED_STAT
com LED_STAT ;Invertieren von LED_STAT
out PORTB, LED_STAT ;Ausgabe von LED_STAT an PORTB -> LEDs an bzw. aus
pop R17 ;Ruecksichern von R17
pop R16 ;Ruecksichern von R16(SREG)
out SREG, R16 ;Ruecksichern von SREG
pop R16
reti
--- werde ueber Weihnachten nicht zu Haus sein und somit auch nicht antworten koennen ---
robo_wolf
28.12.2009, 16:36
so habe nun ein weiteres Miniprogramm.
diesmal mit Ein und Austaster
;***** STK500 Lernprogramm Nr.2
;*** Aufgabe: alle LEDs mit einem Taster auf dem STK500 an ubd mit einem 2. Taster ausschalten
;*** zum Entprellen sollen Interrupts(INT0 und INT1) benutzt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary register
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
rjmp INT0_ISR ;2 Int0-Interrupt
rjmp INT1_ISR ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
RESET:
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, temp
ldi r16, HIGH(RAMEND)
out SPH, temp
ldi temp, (1<<ISC00)|(1<<ISC01)|(1<<ISC10)|(1<<ISC11)
out MCUCR, temp ;Interrupt INT0 und INT1 konfiguriert
ldi temp, (1<<INTF0)|(1<<INTF1)
out GIFR, temp ;InterruptFlagregister geloescht
ldi temp, (1<<INT0)|(1<<INT1)
out GICR, temp ;Interrupt INT0 und INT1 aktiviert
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out DDRD, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out PORTD, temp ;PullUp an PortD einschalten
out DDRB,Temp ;PORTB als Ausgang
out PORTB, temp ;PORTB (LEDs) aus
sei ;Interrupts zulassen
MAIN:
rjmp MAIN ;Die Schleife ruft mit dem Sprungbefehl
;rjmp sich ständig selbst auf.(endlos)
INT0_ISR:
push R16 ;Inhalt von R16 auf Stack ablegen
in R16, SREG ;Statusregister in R16 lesen
push R16 ;Inhalt von R16(SREG) auf den Stack ablegen
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out PORTB, temp ;Ausgabe an PORTB -> LEDs aus
pop R16 ;Ruecksichern von R16(SREG)
out SREG, R16 ;Ruecksichern von SREG
pop R16
reti
INT1_ISR:
push R16 ;Inhalt von R16 auf Stack ablegen
in R16, SREG ;Statusregister in R16 lesen
push R16 ;Inhalt von R16(SREG) auf den Stack ablegen
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out PORTB, temp ;Ausgabe von LED_STAT an PORTB -> LEDs an
pop R16 ;Ruecksichern von R16(SREG)
out SREG, R16 ;Ruecksichern von SREG
pop R16
reti
mare_crisium
30.12.2009, 12:10
robo_wolf,
als Folgeaufgabe böte sich Folgendes an:
Ein allgemein anwendbares Entprellverfahren. So wie Du's jetzt machst, kannst Du immer nur maximal zwei Taster entprellen, die obendrein auch noch die beiden wertvollen externen Interrupts belegen. Deshalb ist ein Verfahren sehr nützlich, mit dem Taster entprellt werden, die einfach an einen Input-Port angeschlossen sind. Dabei kannst Du auch gleich die ersten Schritte mit Timer/Countern machen.
;***** STK500 Lernprogramm Nr.3
;*** Aufgabe: alle LEDs mit einem Taster auf dem STK500 an und mit einem 2. Taster ausschalten
;*** zum Entprellen wird Timer/Counter0 genutzt.
; Die Interrupts INT0 und INT1 werden nicht benutzt. Der Overflow-Interrupt des TimerCounter0
; wird aktiviert.
; Bei jedem TOV0-Interrupt wird der Zustand von Taster0 und Taster1 abgelesen.
; Für jeden Taster wird ein Zustandsregister eingerichtet. Wenn bei einer Ablesung Taster0
; gedrückt ist, wird in das Zustandsregister von Taster0 von links eine 1 eingeschoben
; (z.B. <ror>-Anweisung, vorher CARRY-Flag setzen). Wenn bei einer Ablesung Taster0 nicht
; gedrückt ist, wird in das Register von rechts eine Null eingeschoben. Die Taste gilt nur dann als gedrückt,
; wenn der Inhalt des Registers grösser als 0x0F ist.
; Das Entprellen bei Taster1 wird analog gelöst.
;
; Mit dem Prescaler spielen, um verschiedene Reaktionsgeschwindigkeiten auzuprobieren
; Mit dem Schaltgrenzwert des Zustandsregisters spielen, um zeitlich symmetrisches und
; unsymmetrisches Verhalten zu erproben
;***
Wär' das was? Weiterhin viel Spass beim Assemblern :-) !
mare_crisium
robo_wolf
01.01.2010, 20:05
hallo mare_crisium,
klar doch wollte eh das Programm abaendern, dass jeder Taster verwendet werden kann. Somit muss ich mich um ein anderes entprellen kuemmern.
Aber eine Schaltung habe ich noch zwischenzeitlich gemacht.
Angelehnt an das Hundegeschirr von unseren Berner.
hier dieser Code:
;***** STK500 Lernprogramm Nr.3
;*** Aufgabe: alle LEDs mit einem Taster auf dem STK500 schalten
;*** 1. Tastendruck: LEDs einschalten
;*** 2. Tastendruck: LEDs blinken
;*** 3. Tastendruck: LEDs ausschalten
;*** zum Entprellen soll ein Interrupt(Int0) benutzt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary register
.def Tast_Stat = r17 ; Zaehler fuer Taste
.def Delay = R18 ; Wartezeit
.def Delay2 = R19 ; Wartezeit
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
rjmp INT0_ISR ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
RESET:
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
ldi temp, (1<<ISC00)|(1<<ISC01)|(1<<SE)
out MCUCR, temp ;Interrupt INT0 und Sleep enable konfiguriert
ldi temp, 1 << INTF0 ;InterruptFlagregister geloescht
out GIFR, temp
ldi temp, 1 << INT0 ;Interrupt INT0 aktiviert
out GICR, temp
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out DDRD, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out PORTD, temp ;PullUp an PortD einschalten
out DDRB,Temp ;PORTB als Ausgang
out PORTB, temp ;PORTB (LEDs) aus
sei ;Interrupts zulassen
MAIN:
rcall TESTE
rjmp MAIN
TESTE:
clr temp ;loesche temp
inc temp ;increment von temp - "1"
cp Tast_Stat,temp ;ist TastStat = 1 dann Rufe Sub LED_AN auf, sonst ueberspringe naechsten Befehl
breq LED_AN
inc temp ;increment von temp - "2"
cp Tast_Stat,temp ;ist TastStat = 2 dann Rufe Sub LED_BLINK auf, sonst ueberspringe naechsten Befehl
breq LED_BLINK
inc temp ;increment von temp - "3"
cp Tast_Stat,temp ;ist TastStat = 3 dann Rufe Sub LED_AUS auf, sonst ueberspringe naechsten Befehl
breq LED_AUS
sleep
ret
LED_AN: ;loesche temp
clr temp ;setze alle Bit in temp (0b00000000 bzw. 0x00)
out PORTB, temp ;Ausgabe an PortB
sleep
ret
LED_BLINK:
in temp,PORTB ;lese PORTB in temp
com temp ;Einercomplement von temp (ist 0b00000000 > 0b11111111 oder umgekehrt)
out PORTB,temp ;Ausgabe an PortB
Tot_Zeit:
dec Delay ;zaehle ein Register R18 - 0b0000000 runter und springe danach aus der Schleife
brne Tot_Zeit
dec Delay2 ;zaehle ein Register R19 - 0b0000000 runter und springe danach aus der Schleife
brne Tot_Zeit
ret
LED_AUS:
ser temp ;setze alle Bits in temp (0b11111111 bzw. 0xFF)
out PORTB,temp ;Ausgabe an PORTB
clr Tast_Stat ;Loesche Tast_Stat
sleep
ret
INT0_ISR:
ldi Delay, 0b00000001 ;Ruecksprung aus LED_BLINK beschleunigen
ldi Delay2, 0b00000001 ;Ruecksprung aus LED_BLINK beschleunigen
push R16 ;Inhalt von R16 auf Stack ablegen
in R16, SREG ;Statusregister in R16 lesen
push R16 ;Inhalt von R16(SREG) auf den Stack ablegen
inc Tast_Stat
pop R16 ;Ruecksichern von R16(SREG)
out SREG, R16 ;Ruecksichern von SREG
pop R16 ;Ruecksichern von R16
reti
Als naechstes wird das generelle entprelle in Angriff genommen :-)
mare_crisium
03.01.2010, 14:24
robo_wolf,
meine Kommentare sind diesmal ein bisschen länglich ausgefallen ;-), deshalb hab' ich sie als .pdf angehängt.
Ciao,
mare_crisium
robo_wolf
08.01.2010, 18:37
Hallo mare_crisium,
was soll ich sagen...
Als ich heute Deine Kommentare gelesen habe, war ich so richtig "baff" und andererseits wahnsinnig froh, dass Du Dir so viel Zeit genommen hast, mir Erklaerungen und Beispiele fuer mein kleines Programm zu schreiben. Dafuer erst einmal vielen vielen Dank.
Die "ret" Erklaerung sehe ich nun auch von anderen Blickpunkt aus. Habe beim Erstellen des Codes an der Art nach sich ziehende Probleme nicht gedacht.
Man lernt halt nur aus solchen "Fehlern", wenn man sie bewusst gemacht hat.
Die Delays auf 1 zusetzen kam eigentlich nur aus dem Studio-Simulator.
Dort hatte ich gesehen, dass beim Ruechsprung aus der ISR(wenn ISR waehrend LED_BLINK ausgefuehrt wurde) erst noch die Tot_Zeit abgearbeitet wurde, bevor es zu TESTE zurueck "ret" geht.
Naja ich gebe es zu, da hatte ich schon gemerkt, dass der Code nicht ganz so war, wie ich es eigentlich wollte.
Sah aber zu dem Zeitpunkt keinen anderen Weg, das Runterzaehlen der Register zu beschleuigen oder gar zu unterbinden.
Seit meinem letzten Posting hatte ich leider nicht so viel Zeit, wie ich gern wollte.
Hatte mich aber schon an das naechste Programm, wie von Dir vorgeschlagen, die Tasten ohne Interrupt zu entprellen gemacht und da ein wenig probiert.
Das muss ich nun auch noch einmal gruendlich ueberdenken bzw ueberarbeiten.
Zuvor moechte ich mir Deine Codeaenderungen im Simulator genau anschauen.
Habe da sicher noch einige Fragen.... :-)
mare_crisium
08.01.2010, 20:24
Buona sera, Silvio,
freut mich, dass Du Dich trotz allem nicht vom Assembler-Programmieren abbringen lässt ;-). Nur weiter so.
Ciao,
mare_crisium
robo_wolf
10.01.2010, 19:01
Hallo mare_crisium,
habe den Code zum Tasten_3 nach Deinen Vorschlaegen angepasst.
Teil 1war ja im Prinzip nur das Sichern der Register in den Unterprogrammen.
Das Register 17 kann ich nicht mit Sichern, da ich es als Zaehlvariable in der ISR benutze.
Mit R16 – temp kein Problem. Funktioniert:
;***** STK500 Lernprogramm Nr.3c
;*** Aufgabe: alle LEDs mit einem Taster auf dem STK500 schalten
;*** 1. Tastendruck: LEDs einschalten
;*** 2. Tastendruck: LEDs blinken
;*** 3. Tastendruck: LEDs ausschalten
;*** zum Entprellen soll ein Interrupt(Int0) benutzt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary register
.def Tast_Stat = r17 ; Zaehler fuer Taste in ISR
.def Delay = R18 ; Wartezeit
.def Delay2 = R19 ; Wartezeit
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
rjmp INT0_ISR ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
RESET:
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
ldi temp, (1<<ISC00)|(1<<ISC01)
out MCUCR, temp ;Interrupt INT0 konfiguriert
ldi temp, 1 << INTF0 ;InterruptFlagregister geloescht
out GIFR, temp
ldi temp, 1 << INT0 ;Interrupt INT0 aktiviert
out GICR, temp
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out DDRD, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out PORTD, temp ;PullUp an PortD einschalten
out DDRB,Temp ;PORTB als Ausgang
out PORTB, temp ;PORTB (LEDs) aus
sei ;Interrupts zulassen
MAIN:
rcall TESTE
rjmp MAIN
TESTE:
push r16
in r16,SREG
push r16
clr temp ;loesche temp
inc temp ;increment von temp - "1"
cp Tast_Stat,temp ;ist TastStat = 1 dann Rufe Sub LED_AN auf, sonst ueberspringe naechsten Befehl
breq LED_AN
inc temp ;increment von temp - "2"
cp Tast_Stat,temp ;ist TastStat = 2 dann Rufe Sub LED_BLINK auf, sonst ueberspringe naechsten Befehl
breq LED_BLINK
inc temp ;increment von temp - "3"
cp Tast_Stat,temp ;ist TastStat = 3 dann Rufe Sub LED_AUS auf, sonst ueberspringe naechsten Befehl
breq LED_AUS
rjmp TESTE_EXIT
LED_AN: ;loesche temp
clr temp ;setze alle Bit in temp (0b00000000 bzw. 0x00)
out PORTB, temp ;Ausgabe an PortB
rjmp TESTE_EXIT
LED_BLINK:
in temp,PORTB ;lese PORTB in temp
com temp ;Einercomplement von temp (ist 0b00000000 > 0b11111111 oder umgekehrt)
out PORTB,temp ;Ausgabe an PortB
Tot_Zeit:
dec Delay ;zaehle ein Register R18 - 0b0000000 runter und springe danach aus der Schleife
brne Tot_Zeit
;dec Delay2 ;zaehle ein Register R19 - 0b0000000 runter und springe danach aus der Schleife
;brne Tot_Zeit
rjmp TESTE_EXIT
LED_AUS:
ser temp ;setze alle Bits in temp (0b11111111 bzw. 0xFF)
out PORTB,temp ;Ausgabe an PORTB
clr Tast_Stat ;Loesche Tast_Stat
sleep
rjmp TESTE_EXIT
TESTE_EXIT:
pop r16
out SREG,r16
pop r16
ret
INT0_ISR:
push R16 ;Inhalt von R16 auf Stack ablegen
in R16, SREG ;Statusregister in R16 lesen
push R16 ;Inhalt von R16(SREG) auf den Stack ablegen
inc Tast_Stat
pop R16 ;Ruecksichern von R16(SREG)
out SREG, R16 ;Ruecksichern von SREG
pop R16 ;Ruecksichern von R16
reti
Der 2. Tipp macht mir jedoch Kopfzerbrechen(Seite 5- TESTE_02 bzw. TESTE_03)
Beim Simulieren im AVR-Studio bekomme ich Speicherfehler, da das Programm den STACK durcheinander bringt...?)
;***** STK500 Lernprogramm Nr.3b
;*** Aufgabe: alle LEDs mit einem Taster auf dem STK500 schalten
;*** 1. Tastendruck: LEDs einschalten
;*** 2. Tastendruck: LEDs blinken
;*** 3. Tastendruck: LEDs ausschalten
;*** zum Entprellen soll ein Interrupt(Int0) benutzt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary register
.def Tast_Stat = r17 ; Zaehler fuer Taste
.def Delay = R18 ; Wartezeit
.def Delay2 = R19 ; Wartezeit
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
rjmp INT0_ISR ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
RESET:
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
ldi temp, (1<<ISC00)|(1<<ISC01)
out MCUCR, temp ;Interrupt INT0 konfiguriert
ldi temp, 1 << INTF0 ;InterruptFlagregister geloescht
out GIFR, temp
ldi temp, 1 << INT0 ;Interrupt INT0 aktiviert
out GICR, temp
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out DDRD, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out PORTD, temp ;PullUp an PortD einschalten
out DDRB,Temp ;PORTB als Ausgang
out PORTB, temp ;PORTB (LEDs) aus
sei ;Interrupts zulassen
MAIN:
rcall TESTE
rjmp MAIN
TESTE:
push r16
in r16,SREG
push r16
; In Abhängigkeit von Tast_Stat Aktivität auswählen
clr temp ;
inc temp ; temp := Vergleichszahl; Anfangswert Eins
cp Tast_Stat,temp
brne TESTE_02 ; Sprung, wenn TastStat <> 1
rcall LED_EIN
rjmp TESTE_EXIT
TESTE_02:
; Tast_Stat > 1
inc temp
cp Tast_Stat,temp ; Vergleichszahl := 2
brne TESTE_03 ; Sprung, wenn TastStat <> 2
rcall LED_BLINK
rjmp TESTE_EXIT
TESTE_03:
; Tast_Stat > 3
inc temp ; Vergleichszahl := 3
cp Tast_Stat,temp
brne TESTE_EXIT ; Sprung, wenn TastStat <> 3
rcall LED_AUS
LED_EIN: ;loesche temp
clr temp ;setze alle Bit in temp (0b00000000 bzw. 0x00)
out PORTB, temp ;Ausgabe an PortB
rjmp TESTE_EXIT
LED_BLINK:
in temp,PORTB ;lese PORTB in temp
com temp ;Einercomplement von temp (ist 0b00000000 > 0b11111111 oder umgekehrt)
out PORTB,temp ;Ausgabe an PortB
;Tot_Zeit:
;dec Delay ;zaehle ein Register R18 - 0b0000000 runter und springe danach aus der Schleife
;brne Tot_Zeit
;dec Delay2 ;zaehle ein Register R19 - 0b0000000 runter und springe danach aus der Schleife
;brne Tot_Zeit
rjmp TESTE_EXIT
LED_AUS:
ser temp ;setze alle Bits in temp (0b11111111 bzw. 0xFF)
out PORTB,temp ;Ausgabe an PORTB
clr Tast_Stat ;Loesche Tast_Stat
rjmp TESTE_EXIT
TESTE_EXIT:
pop r16
out SREG,r16
pop r16
ret
INT0_ISR:
push R16 ;Inhalt von R16 auf Stack ablegen
in R16, SREG ;Statusregister in R16 lesen
push R16 ;Inhalt von R16(SREG) auf den Stack ablegen
inc Tast_Stat
pop R16 ;Ruecksichern von R16(SREG)
out SREG, R16 ;Ruecksichern von SREG
pop R16 ;Ruecksichern von R16
reti
eventuell habe ich auch einen Bug mit eingebaut.
Nach dem 1. Abarbeiten der ISR kommt das LED_EIN.
Hier wird wieder durch das rjmp der Programmzaehler auf den Stack abgelegt.
Danach der Sprung zum TESTE_EXIT.
Dabei wird ins SREG 0X00 (ein Teil vom Programmzaehler - rjmp LED_EIN) rein geschrieben.
-muesste da nicht doch LED_EIN mit „ret“ beendet werden?
Besserwessi
10.01.2010, 19:25
Das sichern des SREG und ggf, anderer register braucht man nur für ISRs.
Wenn man es trotzdem macht, verschwendet man erstmal Platz auf den Stack, und man kann leicht Fehler machen sodass es dann den Stack richtig durcheinander bringt.
Dann ist da noch ein Fehler dirn: Unterprogramme die man mir Rcall aufruft, bendet man mit RET, nicht mit einem RJMP nach teste_exit.
mare_crisium
11.01.2010, 00:05
Silvio,
ich habe mir das Lernprogramm 3c ungeändert aus Deinem Posting in den Simulator des AVRStudio geladen und durch alle Zustände von "Tast_Stat" durchlaufen lassen. Es hat einwandfrei funktioniert :-) . Nur die "sleep"-Anweisung war wirkungslos, weil Du im MCUCR-Register das Bit SE nicht auf "1" gesetzt hast. Aber selbst, wenn ich im Simulator SE in MCUCR auf "1" setze, läuft das Programm völlig richtig weiter.
Lernprogramm 3b muss noch korrigiert werden. Zweck der Übung war ja, die einzelnen Funktionen "LED_EIN", "LED_AUS" und "LED_BLINK" in je eine selbstständige Prozedur auszulagern. Wenn man das tut, muss man jede dieser Prozeduren auch mit einer eigenen "ret"-Anweisung abschliessen, sonst kommt der Stack tatsächlich durcheinander.
So könnte das aussehen
;***** STK500 Lernprogramm Nr.3bb
;*** Aufgabe: alle LEDs mit einem Taster auf dem STK500 schalten
;*** 1. Tastendruck: LEDs einschalten
;*** 2. Tastendruck: LEDs blinken
;*** 3. Tastendruck: LEDs ausschalten
;*** zum Entprellen soll ein Interrupt(Int0) benutzt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary register
.def Tast_Stat = r17 ; Zaehler fuer Taste
.def Delay = R18 ; Wartezeit
.def Delay2 = R19 ; Wartezeit
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
rjmp INT0_ISR ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
RESET:
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
ldi temp, (1<<ISC00)|(1<<ISC01)
out MCUCR, temp ;Interrupt INT0 konfiguriert
ldi temp, 1 << INTF0 ;InterruptFlagregister geloescht
out GIFR, temp
ldi temp, 1 << INT0 ;Interrupt INT0 aktiviert
out GICR, temp
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out DDRD, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out PORTD, temp ;PullUp an PortD einschalten
out DDRB,Temp ;PORTB als Ausgang
out PORTB, temp ;PORTB (LEDs) aus
sei ;Interrupts zulassen
MAIN:
rcall TESTE
rjmp MAIN
TESTE:
push r16
in r16,SREG
push r16
; In Abhängigkeit von Tast_Stat Aktivität auswählen
clr temp ;
inc temp ; temp := Vergleichszahl; Anfangswert Eins
cp Tast_Stat,temp
brne TESTE_02 ; Sprung, wenn TastStat <> 1
rcall LED_EIN
rjmp TESTE_EXIT
TESTE_02:
; Tast_Stat > 1
inc temp
cp Tast_Stat,temp ; Vergleichszahl := 2
brne TESTE_03 ; Sprung, wenn TastStat <> 2
rcall LED_BLINK
rjmp TESTE_EXIT
TESTE_03:
; Tast_Stat > 3
inc temp ; Vergleichszahl := 3
cp Tast_Stat,temp
brne TESTE_EXIT ; Sprung, wenn TastStat <> 3
rcall LED_AUS
TESTE_EXIT:
pop r16
out SREG,r16
pop r16
ret
/*-----------------------
PROZEDUR LED_EIN
Die Prozedur LED_EIN setzt im Register "temp" alle Bits auf 0 und
gibt "temp" an PORTB aus.
Eingangsgrössen
temp enthält den aktuellen LED-Zustand
Ausgangsgrössen
temp enthält den LED-Zustand 0x00 = alle LEDs eingeschaltet
geänderte Register
temp
geänderte Ports
PORTB
*/
LED_EIN:
push r19
in r19,SREG
clr temp ;setze alle Bit in temp (0b00000000 bzw. 0x00)
out PORTB, temp ;Ausgabe an PortB
out SREG,r19
pop r19
ret
/*-----------------------
PROZEDUR LED_AUS
Die Prozedur LED_AUS setzt im Register "temp" alle Bits auf 1 und
gibt "temp" an PORTB aus.
Eingangsgrössen
temp enthält den aktuellen LED-Zustand
Ausgangsgrössen
temp enthält den LED-Zustand 0xFF = alle LEDs ausgeschaltet
geänderte Register
temp
geänderte Ports
PORTB
*/
LED_AUS:
push r19
in r19,SREG
ser temp ;setze alle Bits in temp (0b11111111 bzw. 0xFF)
out PORTB,temp ;Ausgabe an PORTB
clr Tast_Stat ;Loesche Tast_Stat
out SREG,r19
pop r19
ret
/*-----------------------
PROZEDUR LED_BLINK
Die Prozedur LED_BLINK invertiert im Register "temp" alle Bits und
gibt den neuen Inhalt von "temp" an PORTB aus.
Eingangsgrössen
temp enthält den aktuellen LED-Zustand
Ausgangsgrössen
temp enthält den invertierten LED-Zustand = alle LEDs umgeschaltet
geänderte Register
temp
geänderte Ports
PORTB
*/
LED_BLINK:
push r19
in r19,SREG
in temp,PORTB ;lese PORTB in temp
com temp ;Einercomplement von temp (ist 0b00000000 > 0b11111111 oder umgekehrt)
out PORTB,temp ;Ausgabe an PortB
;Tot_Zeit:
;dec Delay ;zaehle ein Register R18 - 0b0000000 runter und springe danach aus der Schleife
;brne Tot_Zeit
;dec Delay2 ;zaehle ein Register R19 - 0b0000000 runter und springe danach aus der Schleife
;brne Tot_Zeit
out SREG,r19
pop r19
ret
INT0_ISR:
push R19 ;Inhalt von R19 auf Stack ablegen
in R19, SREG ;Statusregister in R19 lesen
inc Tast_Stat
out SREG, R19 ;Ruecksichern von SREG
pop R19 ;Ruecksichern von R19
reti
So müsste es jetzt funktionieren.
Ciao,
mare_crisium
robo_wolf
17.01.2010, 18:06
so mal wieder was von mir ....
Wie schon angekuendigt, wollte ich ohne Interrupt ein Programmcode schreiben.
Dabei sollen die LEDs mit dem jeweils auf dem STK500 befindlichen Taster geschaltet werden.
Also druecke Taste - jeweilige LED wird je nach vorherigen Zustand umgeschaltet.
LED an wird LED aus
LED aus wird LED an
Ablauf:
Im Programm selbst frage ich in der MAIN den TastenPort ab und lese den Inhalt in ein 1 Register,
invertiere dieses und schiebe den Inhalt in ein 2. Register.
Teste dann ob Taste gedrueckt ist, wenn ja springe in Warte und zaehle dort ein Register runter,
danach(RE_TEST) lese erneut den TastenPort in das 1. Register aus invertiere den Inhalt,
vergleiche dann mit Inhalt des 2. Registers(erstes Auslesen), wenn gleich springe zu LED_TOGGLE.
In LED_TOGGLE wird der LED_PORT in das LED_Stat-Register gelesen.
Nun wird mittels Exklusiv-ODER das LED_Stat-Register mit dem TAST_Stat-Register umgeschrieben.
LED=1 Tast=1 == LED=0
LED=0 Tast=0 == LED=0
LED=0 Tast=1 == LED=1
LED=1 Tast=0 == LED=1
Dann wird der Inhalt des LED_Stat an den LED_PORT ausgegeben.
Als naechstes wird nun ueberprueft, ob die gedrueckte Taste noch immer gedrueckt ist.
Falls das sein sollte kommt der Sprung in eine Schleife, die permanent die Tasten abfragt, bis die urspruenglich gedrueckte Taste losgelassen wurde.
Wenn das passiert kommt der Sprung zurueck in die MAIN, wo das Spiel wieder von vorn beginnen kann.
Hier nun der Code...
;***** STK500 Lernprogramm Nr.4
;*** Aufgabe: die der jeweiligen Taste zugeordnete LED auf dem STK500 schalten
;*** 1. Tastendruck: LEDs einschalten
;*** 2. Tastendruck: LEDs ausschalten
;*** zum Entprellen soll kein Interrupt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary register
.def Tast_Stat = r17 ; Tasten Status
.def Tast_Stat1 = r18
.def LED_Stat = R19 ; LED Status
;
.equ LED_PORT = PORTB ; LEDs
.equ LED_DDR = PORTB-1 ; DataDirectory fuer LEDs
.equ TAST_PORT= PORTD ; Tasten
.equ TAST_DDR = PORTD-1 ; DataDirectory fuer TastenEingang
.equ TAST_PIN = PORTD-2 ; TastenEingang
;
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
reti ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
;*****
RESET:
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out TAST_DDR, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out TAST_PIN, temp ;STK500 schaltet gegen GND - Taste gedreuckt (Pin==0)
out TAST_PORT, temp ;PullUp an PortD einschalten
out LED_DDR,Temp ;PORTB als Ausgang
out LED_PORT, temp ;PORTB (LEDs) aus
MAIN:
in Tast_Stat, TAST_PIN ; lese von PinD und speicher den Inhalt in Tast_Stat
com Tast_Stat ; invertiert fuer spaetere EOR-Berechnung
mov Tast_Stat1, Tast_Stat ; schiebe den Inhalt von Tast_Stat in Tast_Stat2
cpi Tast_Stat, 0b00000000 ; vergleiche Tast_Stat mit 0b00000000, wenn ungleich (Taste gedrueckt) springe zu
WARTE
brne WARTE
rjmp MAIN
WARTE:
push r25 ; Register auf Stack sichern
in r25, SREG
push r25
WARTE_0: ; Wartezeit zum Entprellen
dec r25 ; Schleife decrement Register 25
cpi r25, 0b00000000
breq WARTE_ZEIT_EXIT
rjmp WARTE_0
WARTE_ZEIT_EXIT: ; Ausgang aus Warteschleife
pop r25 ; Register vom Stack holen
out SREG, r25
pop r25
RE_TEST: ; 2. Test - Vergleich ob Taste noch immer gedrueckt
in Tast_Stat, TAST_PIN ; erneute Abfrage von Tasten und Vergleich mit erster Abfrage
com Tast_Stat ; invertiert fuer spaetere EOR-Berechnung
cp Tast_Stat, Tast_Stat1 ; wenn gleich, springe zu LED_TOGGLE
breq LED_TOGGLE
rjmp MAIN
LED_TOGGLE: ; schaltet LED um
in LED_Stat, LED_PORT ; lese LEDs aus und speichere in LED_Stat
eor LED_Stat, Tast_Stat ; ExclusivODER - schalte die Ausgaenge , wo Tasten gedrueckt werden um
; LED=1 Tast=1 == LED=0
; LED=0 Tast=0 == LED=0
; LED=0 Tast=1 == LED=1
; LED=1 Tast=0 == LED=1
out LED_PORT, LED_Stat
cp Tast_Stat, Tast_Stat1 ; vergleiche ob Taste losgelassen
breq WARTE2 ; wenn nicht losgelassen, springe in WARTE2
rjmp MAIN
; Abfrage von Tasten und Vergleich ob die urspruenglich gedrueckte Taste noch immer gedrueckt ist
; neu gedrueckte Taste wird waehrend die urspruenglich gedrueckte Taste noch gedrueckt ignoriert
; ist urspruenglich gedrueckte Taste gedrueckt wird die Schleife nicht verlassen
WARTE2:
RE_TEST_2:
in Tast_Stat, TAST_PIN
com Tast_Stat ; invertiert fuer spaetere EOR-Berechnung
and Tast_Stat, Tast_Stat1 ; Bitmaske - urspruenglich gedrueckte Taste
cp Tast_Stat, Tast_Stat1
breq WARTE2
rjmp MAIN
PS.
Einen Zustand habe ich nun noch nicht bedacht.
Es kann keine weitere Taste gedrueckt werden, solange die erste Taste nicht losgelassen wurde.
Das ist von mir in dem Programm zwar so gewollt, weil ich mir noch ueber die Ausfuehrung noch nicht Gedanken gemacht habe.
Haette ich das im jetzigen Code nicht verriegelt, wueder mir die weitere Taste, die erste LED wieder umschalten.
Eine Frage noch an die Experten:
Kann man im AVR-Studio die Zeilennummern aktivieren?
-> es macht die Fehlersuche leichter
mare_crisium
20.01.2010, 22:50
robo_wolf,
als erstes fiel mir am Lernprogramm Nr. 4 auf, dass Du nach dem Label 'WARTE' diese "push"-Anweisungen eingebaut hast. Die brauchst Du gar nicht. Auf die Gefahr hin, Dir nichts Neues zu erzählen, habe ich trotzdem 'mal ein paar Erklärungen zum Stack und zu den "call"-, "ret"-, "push"- und "pop"-Anweisungen aufgeschrieben. Es kann ja nicht schaden ... ;-).
Den Rest Deines Programms kommentiere ich später.
mare_crisium
P.S.: Ich hab's bisher noch nicht hingekriegt, die Zeilennummern anzeigen zu lassen. Ich habe mich bisher immer mit der kleinen Anzeige unten rechts beholfen, die die Zeilennummer beim Cursor angibt.
robo_wolf
21.01.2010, 22:22
Hallo mare_crisium,
vielen Dank für doch sehr ausfuehrliche Erlaeuterung des Stacks.
Sicher war mir die Funktion und Arbeitsweise schon vertraut.Trotzdem fand ich die Erklaerungen noch vertiefend und leicht verstaendlich.
Aber auch andere User lesen unsere Zeilen und werden sicher ueber so manche verstaendlich geschriebene Erlaeuterung dankbar sein.
Jetzt wo Du mich darauf aufmerksam gemacht hast, sehe ich auch, dass keine Subs im Programm 4 vorhanden sind. Will das Programm eh noch erweitern und eventuell etwas umstellen... Da werde ich doch wieder Unterprogramme mit verwenden.
mare_crisium
21.01.2010, 23:08
Danke für Deinen freundlichen Kommentar, robo_wolf. Leider interessieren sich nicht mehr sehr viele für Assemblerprogrammierung, wie man an der Anzahl downloads sieht. C und Konsorten führen halt viel schneller zu sichtbaren Erfolgen; Assembler erfordert Geduld und Übung. Beides ist hierzulande jüngst etwas aus der Mode gekommen ;-) .
Wenn Du sowieso noch einmal Hand an Dein Programm Nr. 4 legen willst, wie wär's, wenn Du versuchtest, die Tasten von einem Dienstprogramm ablesen zu lassen, dass in regelmässigen Abständen von einem Timer-Interrupt ausgelöst wird? Die Timer wirst Du noch sehr häufig brauchen. Je früher Du Dich mit denen anfreundest, desto besser ;-) !
Ciao,
mare_crisium
robo_wolf
23.01.2010, 17:41
Hallo mare_crisium,
nehme deinen Rat mit den Timer gern an.
Zum Assembler bin ich gerade wegen der Hardwarenaehe gekommen.
Hatte mich auch erst an C und Bascom probiert.
Da blieben fuer mich aber einige Fragen offen. Es funktionierte zwar aber die Hintergruende bleiben da auf der Strecke.
Fuer einige vielleicht nicht wichtig, fuer mich aber schon.
Wenn mann die Hintergruende kennt, kann man besser aggieren. - ist meine Meinung - fuer mich ist das Ganze "nur" Hobby und das soll ja auch Spass machen.
Ausserdem finde ich, ist es aehnlich wie in der Schule Mathematik. Wenn man die Hintergruende kennt, braucht man nicht alles auswendig lernen(was eh keinen Sinn macht) - man kann sich seine Loesungen erarbeiten.
Eine Frage noch zum STK500. Es ist doch sicher sinnvoll auf externen Takt umzusteigen. Was fuer einen Quarz empfiehlst du mir? 4, 8, ??? MHz
mare_crisium
23.01.2010, 18:11
Guten Abend, robo_wolf,
genauso geht mir's auch: Ich will eben gern wissen, was der MC so treibt ;-). Dazu kommt, dass das Assemblerprogrammieren umso zügiger voran, je mehr sorgfältig ausgetestete Module man sich im Laufe der Zeit aufbaut. Das grösste Programm, das ich so geschrieben habe ist 13kB gross. Der Quelltext (alle Module zusammengerechnet und inkl. Kommentare) ist 13000 Zeilen lang.
Wegen des Quarzes? Na, wenn schon dann "the full monty"! Nimm ruhig die 16MHz, die Dein 8515 laut Datenblatt maximal vertägt. Meine ATmegas auf dem STK500 laufen auch so.
Ciao,
mare_crisium
P.S.: Zu Erleichterung der Parameterberechnung für den Timer hänge ich mal die Rechentabelle an, die ich mir dafür gebastelt habe. Nur die gelb unterlegten Felder sind für die Eingabe.
robo_wolf
24.01.2010, 13:31
Hallo mare_crisium,
in die kleine Tabelle hast du ja ordentlich was reingepackt.
Danke
Hatte leider ein paar technische Probleme mit dem AVR-Studio.
Sind aber nun geloest. :-)
Welches Projekt hat denn 13000 Codezeilen gebraucht?
mare_crisium
24.01.2010, 16:17
robo_wolf,
das ist ein Programm, das mit 16MHz auf einem ATmega16 läuft und zwei optische Maussensoren auswertet. Es berechnet alle 200ms aus den Maus-Messwerten eine komplette Koppelnavigation für das Fahrzeug, an dem die Sensoren montiert sind. Es hat ausserdem eine Schnittstelle zum TWI-Bus, über den andere Busteilnehmer die berechneten Daten (Ort, Orientierung, Linear- und Drehgeschwindigkeit) abfragen können. Leider sind die Messdaten der Maussensoren nicht so genau, wie ich's erhofft hatte :-(. Bin aber noch am Experimentieren.
Ciao,
mare_crisium
mare_crisium
25.01.2010, 19:43
robo_wolf,
hier die Kommentare zu Deinem Lernprogramm Nr. 4, diesmal in graphischer Form. Die graphische Darstellung ist manchmal ein gutes Mittel, um die Struktur des Programms zu durchschauen. Oft sieht man dann wieder den Wald und lässt sich von den vielen Bäumen nicht mehr so durcheinanderbringen ;-).
Es gibt verschiedene Normen bzgl. der Symbole, die für Abfrage, Anweisungen, Sprungmarken usw. verwendet werden sollten. Meine Darstellung ist "Freistil".
Ciao,
mare_crisium
oberallgeier
25.01.2010, 20:05
13 000 :-b :-b :-b
und ich dachte immer, die 3000 oder mehr von meinem Dottie seien schon viel. Und davon sind vermutlich mehr als 60 % Kommentare. Alle Achtung!
... nicht ganz normenkonform ...Ich mache das auch nicht korrekt. Bin halt die Nassi-Shneiderman gewöhnt . . . und versuche mich, etwas sehr selten, in UML.
mare_crisium
27.01.2010, 19:11
Oberallgeier,
ja, ist schon ne ganze Menge! Ich wollte damit auch nur sagen, dass der Quelltext auch bei ganz harmlosen Projekten schon ziemlich lang ;-) werden kann. Um sowas fehlerfrei zum Laufen zu bringen, braucht man eben
1. eine übersichtliche, vorzugsweise modulare, Programmstruktur
2. einzeln, vorm Einbinden ins Gesamtprogramm, ausgetestete Programm-Module.
Bei diesem Austesten findet man Fehler sehr viel schneller, wenn man sich selbst bestimmte Regeln für das Programmieren vorgibt und sie dann sorgfältig einhält.
mare_crisium
robo_wolf
29.01.2010, 07:07
so nach laengerer Zeit nun endlich das Programm mit Timer0
;***** STK500 Lernprogramm Nr.4c
;*** Aufgabe: die der jeweiligen Taste zugeordnete LED auf dem STK500 schalten
;*** 1. Tastendruck: LEDs einschalten
;*** 2. Tastendruck: LEDs ausschalten
;*** zum Entprellen soll kein Interrupt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary Register
.def Tast_Stat0 = r17 ; Tasten Status
.def Tast_Stat = r18
.def LED_Stat = r19 ; LED Status
.def Tast_Sperre = r0
.def Aus = r20
;
; TimerWerte(bei 1MHz - ca. 0,1sec/Takt)
;.equ Timerwert = -50 ; Timerschritte bis zum Überlauf(fuer Simulatortest)
.equ Timerwert = 61 ; Timerschritte bis zum Überlauf
.def tsw = r5 ; Timer Startwert (Reload)
;
;
.equ LED_PORT = PORTB ; LEDs
.equ LED_DDR = PORTB-1 ; DataDirectory fuer LEDs
.equ TAST_PORT= PORTD ; Tasten
.equ TAST_DDR = PORTD-1 ; DataDirectory fuer TastenEingang
.equ TAST_PIN = PORTD-2 ; TastenEingang
;
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
reti ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
rjmp TIMER0_OVF ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
;*****
RESET:
ldi temp,Timerwert
mov tsw,temp
out TCNT0,tsw
; ldi temp,(1<<CS00) ; Taktfrequenz ohne Vorteiler gewaehlt (fuer Simaulatortest)
ldi temp,(1<<CS00)|(1<<CS02) ; Taktfrequenz mit Vorteiler 1024 gewaehlt
out TCCR0,temp
ldi temp,(1<<TOIE0) ; Timer0 Overflow aktivieren
out TIMSK,temp
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out TAST_DDR, Temp ;PORTD als Eingang
mov Tast_Sperre, temp
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out TAST_PIN, temp ;STK500 schaltet gegen GND - Taste gedreuckt (Pin==0)
out TAST_PORT, temp ;PullUp an PortD einschalten
out LED_DDR,Temp ;PORTB als Ausgang
out LED_PORT, temp ;PORTB (LEDs) aus
clr Tast_Stat ;Tast_Stat auf def. Zustand
clr Aus
sei ; globale Interruptfreigabe
MAIN:
rcall TASTEN_TEST
rjmp MAIN
TIMER0_OVF:
push TEMP ; TEMP auf Stack sichern
in TEMP,SREG ; SREG auf Stack sichern
push TEMP ;
in Tast_Stat0, TAST_PIN
com Tast_Stat0 ; invertiere
out TCNT0,TSW ; Timer0-Startwert setzen
pop TEMP ;
out SREG,TEMP ; SREG zurueckschreiben
pop TEMP ; TEMP zurueckschreiben
reti
TASTEN_TEST:
mov Tast_Stat, Tast_Stat0
cp Tast_Stat, Aus ; Vergleich - (0b11111111->keine Taste)
brne LED_TOGGLE ; bei != rufe LED_TOGGLE auf
ret
LED_TOGGLE: ; schaltet LED um
in LED_Stat, LED_PORT ; lese LEDs aus und speichere in LED_Stat
com LED_Stat ; invertiere
eor Tast_Stat, Tast_Sperre ; gedrueckt gehaltene Taste sperren
; LED=1 Tast=1 == LED=0
; LED=0 Tast=0 == LED=0
; LED=0 Tast=1 == LED=1
; LED=1 Tast=0 == LED=1
eor LED_Stat, Tast_Stat ; ExclusivODER - schalte die Ausgaenge , wo Tasten gedrueckt werden um
com LED_Stat ; invertiere
out LED_PORT, LED_Stat ; Ausgabe an LEDs
in Tast_Stat0, TAST_PIN ; Wiedereinlesen fuer Tastensperre
com Tast_Stat0 ; invertiere
mov Tast_Sperre, Tast_Stat0
ret
Nur das Problem mit der 2.Tasten, wenn eine vorher "gedrueckte" noch gesetzt ist bzw. nicht losgelassen wurde.
Das will ich noch aendern. - Es soll auch eine 2. oder 3. Eingabe moeglich sein, wenn die 1. oder 2. nicht zurueck gesetzt wurde bzw. die Tasten noch immer gedrueckt sind.
robo_wolf
30.01.2010, 14:17
Hallo mare_crisium,
habe mal eine graphische Darstellung gemacht, so wie ich es mir vorstelle.
Bitte verbessere mich, wenn Du Ungereimtheiten oder Fehler siehst.
mare_crisium
30.01.2010, 16:32
robo_wolf,
werd's mir heute abend zu Gemüte führen. Versuche, morgen meine Rückmeldung zu posten.
Ciao,
mare_crisium
mare_crisium
31.01.2010, 18:19
robo_wolf,
an Deiner Grafik kann man gut die Struktur erkennen, die Dir vorschwebt - dafür sind diese grafischen Darstellungen sehr praktisch.
Ich lese Dein Bild so:
Du hast vor, zwei von einander unabhängige Programmteile aufzubauen:
1. einer liest den Tastenzustand aus
2. der andere wertet den Tastenzustand aus und führt abhängig davon eine Aktion aus
(LEDs ein und aus).
Der 1. Teil, der vom Timerinterrupt angestossen wird, ist für die Entprellung zuständig. Dazu gibt's im Anhang noch etwas Ausführlicheres ;-) . Der 2. Teil reagiert auf die Tastenzustände, wie er sie vom 1. Teil "vorgekaut" vorfindet. Diese Einteilung gefällt mir sehr gut, weil sie voneinander unabhängige Aufgaben getrennten Programmteilen zuordnet.
Zum 1. Teil fallen mir noch folgende Gesichtspunkte ein: Jeder Taster hat die beiden Zustände “betätigt“/“nicht betätigt“. Welche Pegel dabei am Portpin anstehen, hängt von der Art des Tasters („Öffner“ oder „Schliesser“) und von der Beschaltung ab. Ein Öffner zwischen Portpin (mit aktiviertem pull-up-Widerstand)und Masse liefert im betätigten Zustand eine logische Eins, ein Schliesser eine logische Null. Um aller möglichen Verwirrung vorzubeugen, ist es gute Praxis, wenn der 1. Programmteil immer denselben logischen Zustand liefert, wenn der Taster betätigt wird, z.B. den Zustand „hoch“ (= logisch Eins), ganz unabhängig davon, ob das jetzt 5V oder 0V am Pin entspricht.
Wenn man's nicht so macht, muss man sich später im 2. Programmteil immer wieder daran erinnern, wie die Hardware aussieht. Als ich noch mit sehr umfangreichen, mit Relais aufgebauten Steuerungen (naja, das war in den 1980ern ;-) ) zu tun hatte, habe ich mich mir mit sowas mehr als einmal fast das Gehirn verstaucht.
Der erste Programmteil ist ein „Treiber“, der die Hardware kapselt. D.h. alle Programmteile, die die Tasterzustände verarbeiten, die der Treiber liefert, brauchen nichts über die Hardware zu wissen. Wird irgendwann mal ein Taster von Öffner auf Schliesser getauscht, dann braucht man nur den 1. Programmteil anzupassen. Alle anderen Teile können so bleiben, wie sie sind.
Im 2. Teil hast Du die möglichen Tastenzustände, die er vorfindet, in den drei hellbraunen Kästchen aufgezählt. Da fehlt aber einer, nämlich der „Taste losgelassen“. Es ist sehr wichtig, sich immer eine vollständige Liste aller möglichen Zustände aufzuschreiben, damit man ja nicht vergisst, für alle Zustände die Reaktion festzulegen. Und wenn man einen Zustand ausser Acht lassen will, dann ist ganz besonders wichtig, genau dafür die Begründung aufzuschreiben. Man glaubt gar nicht, wie schnell man die Gründe für das Weglassen vergessen hat. Liest man dann später das Programm, hält man das Weglassen erstmal für einen Fehler. Es kostet erfahrungsgemäss sehr viel Zeit, bis man schliesslich wieder dahinterkommt, dass man schon damals so ein schlaues Kerlchen gewesen war...
In Deinem Fall hast Du festgelegt, dass nichts passieren soll, wenn der Taster gedrückt gehalten wird oder wenn er dauernd nicht gedrückt ist. Nur, wenn der Taster gerade gedrückt worden ist, soll das LED umgeschaltet werden. Für das Loslassen hast Du keine Reaktion vorgesehen. Das führt dazu, dass die LED bei jedem zweiten Tastendruck wieder ausgeht. Es gibt also keine ein-eindeutige Zuordnung von Taster- und LED-Zustand. Aber - vielleicht wolltest Du's ja so haben :-) .
Noch ein Vorschlag: Lass' uns diese Version erstmal für eine einzige Taste zum Laufen bringen, danach kümmern wir uns um die Behandlung von mehreren Tasten. Da können wir dann man gucken, wie man vorgeht, wenn man mehr Variablen als Register verfügbar hat...
Ciao,
mare_crisium
Edit: Im Anhang den Fehler korrigiert, auf den robo_wolf in seinem Posting vom 01.02.2010 hinweist.
oberallgeier
31.01.2010, 20:05
Hallo mare_crisium,
herrlich - eine ZAK (ZustandsAutomatenKanone), made by mare_crisium. Einfach perfekt. Und ein schönes Lehrstück zur Tastenverwaltung. Ich muss mal sehen, was davon ich abkupfern kann (ich nehme mir einfach diese Freiheit *ggg* - und danke schon mal dafür).
Das Thema interessiert mich, weil ich derzeit seit vielen Tagen (wieder mal) an einem Pacer arbeite - das ist ein Blinker (vllt. mit Piepsmöglichkeit), der mit zwei LEDs und zwei Tastern ausgestattet ist und mir für Lauftraining eine Schrittfrequenz vorgibt. Also eine Art Metronom. Kein Display - ausser den beiden LEDs.
Die Tasten dienen zum Einschalten des aktiven Modus, zum Ausschalten, zum Ändern der Frequenz rauf und runter, zum Ein-/Ausschalten des Piepsers, zum Ändern eines Zeit-Mess-Taktes - weil das Dings ohne Quarz läuft und daher nicht wirklich stabil in der Frequenz ist. Dieses Gimmik ist auch ne Kanone - aber es soll halt alles den Anschein von Perfektion haben. Vielleicht noch mehr Gimmiks.
Nun habe ich meine Tasten nicht nur zu entprellen gehabt, sondern wollte auch in einem Horizont von etwa 2,5 sec wissen, wie lange die Taste schon gedrückt bzw. gelöst wurde. Damit kann/will ich im Menue herumtoben, je nachdem welche Taste in welcher Reihenfolge wie lange gedrückt wurde. Kennen wir von mancher alten Digital-Armbanduhr, von Fahrradcomputern etc. Also stelle ich den Tastenstatus der beiden Tasten TAB und TAU (Taste ABwärts und Taste AUfwärts) im 100 Hz Rhythmus in einer ISR fest - und zähle die jeweiligen Zeitabschnitte (in 10 ms-Schnitten) "gelöst" oder "gedrückt" ab dem letzen Wechsel. Damit kann ich dann recht gut spielen.
Das Ganze läuft dann so auf (m)einem tiny85:
/* >> Sicherung 22Jan10 0930 ...C2..\PCR_80\PCR_80_tmr_x51.c
Es folgt ein Auszug aus einem funktionierenden Teilprogramm
================================================== =================================
Target MCU : ATtiny85
Target Hardware : Pacer ...
Target cpu-frequ. : In der Quelle wählbar
================================================== =================================
Enthaltene Routinen: Nur Timer
...
void TC1TMR_init(void) // Init Tmr/Cntr1, 8-Bit auf ca. 100 Hz = 0,01 s 50
...
ISR(TIM1_COMPA_vect) // Vektor 4 (1 ... 4 !!!) doc S 50
...
================================================== =================================
*** Versionsgeschichte:
====================
x51 22Jan10 0930 Weiter in der Blink-organisation
...
x00 07Dez09 2220 Übernahme vom Original, s.u.,
================================================== =================================
*** Aufgabenstellung : Timer für pacer80
Original: ...C2..\pacer_61\pcr61_tmr_x21.c x21 11feb09
war: ...C2..\wallr_20\WR3_tmr_x03.c 30Nov08
================================================== ============================== */
// ================================================== ==============================
// ================================================== ==============================
// === Initialisierung fuer Timer1 tiny85 ========================================
void TC1TMR_init(void) // Init Tmr/Cntr1, 8-Bit auf ca. 100 Hz = 10 ms
{
TCCR1 |= (1<<CTC1); // Timer im CTC-Mode, Top=OCRA doc S 92
TCCR1 |= (1<<CS12)|(1<<CS11)|(1<<CS10);
// Prescaler Clock1/64 doc S 93
OCR1C = 152; // Preset/Preload = 152 => getestet ca. 0,010s @ 1,0Mhz
TIMSK |= (1<<OCIE1A); // Tmr/Cntr1 CompareA interrupt enabled
//SetBit (PB, rLED); // Testphase: Zeitanzeige durch rLED
}
// ================================================== ==============================
// ================================================== ==============================
// === Nicht unterbrechbare ISR für timer1 =======================================
// Interrupt mit ca. 100 Hz/10 ms. Diese ISR wird benutzt
// A zum Auslesen der Tasten
// Nach Ende der Routine sind die Tastenstati in den Statusbytes
// B Zum Setzen der LEDs
// Es werden beide LEDs durch das vorgegebene Bitmuster bedient ( 0 / 1 )
// AUSNAHME : Wenn
//
ISR(TIM1_COMPA_vect) // Vektor 4 (1 ... 4 !!!) doc S 50
{
// Ab hier mal ähnlicher Aufbau wie bei Timer0
Izeit_1 ++; //
if (Izeit_1 >= 100) //Interrupt-Timer = "Zeithorizont" begrenzen auf
// 100 = Zeit für EINE Sekunde
{
Izeit_1 = 1; // Rückstellen auf Eins
Isecflag = 55; // .. und Sekundenflag setzen
}
// - - Tastenstatistik - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - Es wird geprüft ob eine Taste gedrückt ist - - - - - - - - - - - - - - - -
// WENN diese Taste gedrückt ist, wird einfach bis 254 hochgezählt
// WENN diese Taste NICHT gedrückt ist, wird schnell (z.B.20) runtergezählt
// Änderung 01Jan10 WENN Taste NICHT gedrückt ist, wird *cnt auf Null gesetzt
// - - Zuerst für TAB - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TABsts = (!(PINB & (1<<TAB))); // GENAU so *ggg* -- Status von Taste TAB holen
if (TABsts) // Taste TAB gedrückt?
{
if (TABcnt < 254) // Wenn counter < 250
{
TABcnt ++; // dann hochtackern
nTABcnt = 0; // und Not-TABcount auf Null setzen
}
}
else // Taste TAB ist nicht gedrückt
{
if (nTABcnt < 254) // Wenn not-counter < 250
{
nTABcnt ++; // dann hochtackern
TABcnt = 0; // und TABcount auf Null setzen
}
}
// - - .. und auch für TAU - - - - - - - - - - - - - - - - - - - - - - - - - - -
// . . . .
// - - Die Tastenstati sind ausgelesen - - - - - - - - - - - - - - - - - - - - -
// . . . .
return;
}
// ================================================== ==============================
// ================================================== ==============================
// . . . .
// ================================================== ==============================
// ================================================== ==============================
// ===== ENDE Subroutinen ================================================== ==
// ================================================== ==============================
// ================================================== ==============================
// ===== Sonstige Angaben ================================================== ==
// ================================================== ==============================
#define TAU 1 // Taster "Aufwärts", PB1
#define TAB 2 // Taster "Abwärts", PB2 = "Start"Taste
#define PB PORTB // Kurzwort für PORTB
#define gLED 4 // Grüne LED PB4
// Typ LITEON-LTL1CHKGKNN = CSD-25-3020320
// 572 nm, 45°, IF 20 mA bei UF 2,4 V
// Peak FW 60 mA, DC FW 30 mA
#define rLED 3 // Rote LED auf PB3
#define IsBitSet(ADDR,BIT) (((ADDR)&(1<<BIT))?1:0) // Fragt Bit = 1?
#define IsBitClr(ADDR,BIT) (!((ADDR) & (1<<BIT))) // Fragt Bit = 0?
volatile uint16_t Izeit_1; // Timer1, ähnlich ~0
volatile uint8_t Isecflag; // Sekundenflag...55 = Sekunde in ISR erreicht
// 0 = Sekunde in main übernommen
volatile uint8_t TABsts; // Status derTaste TAB
// = 0 <=> nicht gedrückt, =1 <=> gedrückt
volatile uint8_t TABcnt; // Counter für Taste TAB, 0 ... 255
volatile uint8_t nTABcnt; // Counter für Taste notTAB, 0 ... 255
Dies vielleicht als Anhaltspunkt.
robo_wolf
01.02.2010, 20:33
Hallo mare_crisium,
erst einmal vielen Dank fuer Deine Auswertung und den sehr ausfuehrlichen "endlichen Zustandsautomaten".
(kann es sein, dass sich ein kleiner Fehler eingeschlichen hat?
"Die Entscheidung, ob der aktuelle Tasterzustand „hoch“ oder „tief“ ist, kann als
Mehrheitsentscheidung betrachtet werden: Beim Z=3 war die Mehrheit der Messwerte „hoch“, bei Z=0 war
sie „tief“. Also werden dem Z-Wert 0 der Tasterzustand „hoch“ und dem Z-Wert 3 der „tief“
### muss es hier nicht umegkehrt lauten? ..oder ich habe es doch nicht begriffen..###
zugeordnet. In den Zuständen 1 und 2 herrscht Stimmengleichheit. Deshalb wird festgelegt, dass der
vorhergehende Tasterzustand unverändert bleibt, bis entweder Z=0 oder Z=3 erreicht wird."
Die Ausfuehrungen dazu habe ich in den Grundzuegen begriffen.
Wobei ich zugeben muss, manches mehrmals durchgelesen und durchdacht zuhaben, bis es so weit war. :-)
Meine Vorstellungen des Schaltens eines Tasters hat nicht so viele Zustaende umfasst.
Bei mir gab es nur die Zustaende und deren Aktionen:
1. Taste gedrueckt (Kein Zustandswechsel an den Tasten)
1a. Taste nicht gedrueckt bei LED-Zustand AUS -> LED AUS (keine Aktion)
1b. Taste nicht gedrueckt bei LED-Zustand AN -> LED AN (keine Aktion)
2. Taste nicht gedrueckt "Wechsel auf" Taste gedrueckt -> Umschalten der LED (Zustandswechsel an den Tasten)
2a. Taste nicht gedrueckt auf Taste gedrueckt bei LED-Zustand AUS -> LED AN (Aktion von AUS auf AN)
2b. Taste nicht gedrueckt auf Taste gedrueckt bei LED-Zustand AN -> LED AUS (Aktion von AN auf AUS)
3. Taste nicht gedrueckt "Wechsel auf" Taste gedrueckt und gehalten -> Umschalten der LED (Zustandswechsel an den Tasten)
3a. Taste gedrueckt gehalten bei LED-Zustand AUS -> LED AN (Aktion von AUS auf AN)
3b. Taste gedrueckt gehalten bei LED-Zustand AN -> LED AUS (Aktion von AN auf AUS)
Die abfallende Taste(gedrueckte Taste Wechsel auf Taste nicht gedrueckt) habe ich nicht betrachten und ihr auch keine Aktion angedacht.
Wenn ich meine Zeile aber nun so lese, stimmt das nicht wirklich... und ich muss Dir Recht geben.
" ..und ihr auch keine Aktion angedacht" - ist falsch, es sollte sich "nur" nichts am LED-Zustand aendern.
somit ergaenze ich hier:
4. Taste gedrueckt gehalten und Taste los gelassen (Zustandswechsel an den Tasten)
4a. bei LED-Zustand AUS -> LED AUS (keine Aktion)
4b. bei LED-Zustand AN -> LED AN (keine Aktion)
Beim Thema "Timer_Routine"- (Teil1) sprichst Du von einem Treiber bzw. Kapselung.
Die Idee bzw. den Hinweis, so an die Sache heran zu gehen, finde ich sehr gut.
In meinen Fall waere es eine Art "Eingangstreiber".
Wobei es sich dann foermlich aufdraengt gleiches an Ausgangsseite zu tun.
Zusammen gesagt einen IO-Treiber fuer das STK500...
Leicht gesagt bzw. geschrieben :-) ich weis.
Aber dieses Thema einmal komplett durchdacht und abgearbeiten - kann mann diesen Treiber immer wieder verwenden, in andere Projecte includen.
Der "IO-Treiber" braeuchte dann nur die Informatioen
-- Input oder Output
- Tastenzustaende in eine Inputvariable zu schreiben
- aus einer Outputvariable in den LED-Port zu schreiben.
Den Vorschlag "Lass' uns diese Version erstmal für eine einzige Taste zum Laufen bringen.." stimme ich,
nach dem was ich nun alles gelesen habe, vollkommen zu.
Nach dem Motto kleine Schritte machen, die aber richtig, soll erst einmal nur das Einlesen funktionieren.
-> Nehmen wir also die Taste, welche am PinD0 gekoppelt ist, als einzulesene Taste.
Als Ausgabe die LED an PortB0.
Ferner sind die Besonderheiten des STK500 bekannt.
Tasten sind mit PullUp-Winderstaende versehen:
Taste nicht betaetigt: liefert beim Einlesen eine log 1
Taste betaetigt: liefert beim Einlesen eine log 0
Der LED-Port schaltet LED AUS bei einer log 1
LED AN bei einer log 0.
Die Timer_Routine liest den Zustand von PinD0 und bereitet ihn so auf, dass
Taste nicht betaetigt eine log 0
Taste betaetigt eine log 1
in die Zustandsvariable schreibt.
??? (Bleibt erst einmal zu klaeren, in welcher Zeit die Timer_Routine aufgerufen werden soll? Was ist ein guter Wert?) ???
Die Zustandsvariable wird entsprechend Deiner Ausfuehrungen getestet:
Um eine gedrueckte Taste(log 1) zu erkennen, muss die Timer_Routine 3 mal nacheinander eine log 1 uebergeben.
Um eine nicht gedrueckte Taste(log 0) zu erkennen, muss die Timer_Routine 3 mal nacheinander eine log 0 uebergeben.
so erst einmal die Theorie. Die Praxis folgt....
mare_crisium
03.02.2010, 15:30
robo_wolf,
jau, danke für den Hinweis auf den Fehler - ist schon korrigiert!
Es macht richtig Spass, mitzulesen, wie Du Dich in das Thema hineinwühlst :-) ! Am Besten gefällt mir Deine Schlussfolgerung:
Aber dieses Thema einmal komplett durchdacht und abgearbeiten - kann mann diesen Treiber immer wieder verwenden, in andere Projecte includen.
Zu genau demselben Schluss bin ich nach ein paar Monaten auch gekommen und bin damit seither gut gefahren. Ein Programm-Modul deckt bei mir jeweils eine Funktion komplett ab (im Sinne von Kapselung, wie man sie von objektorientiertem Programmieren her kennt). Z.B. steckt die Handhabung des TWI-Busses und meines eigenen TWI-Protokolls in einem Modul. Wenn ich Daten über den Bus versenden will, schreibe ich sie byteweise in die Ausgangs-FIFO, rufe im TWI-Modul die Prozedur TWI_SEND auf und weiter geht's. Über die ganze interne Abwicklung im Modul brauche ich nie mehr ( ;-) ) Gedanken zu machen.
Die Module werden einfach per "include"-Anweisung eingebunden. Z.B. sieht der "include"-Block in meinem 13kB-Programm so aus:
.include "ASTWI16_V01.asm" ; Modul zur TWI-Abwicklung
.include "ASTWIIF16_V04.asm" ; Interface zwischen Hauptprogramm und TWI-Modul, enthält die Dialogstruktur
.include "FIFOk16_V01.asm" ; Ein- und Ausgangs-Datenpuffer für TWI-Bus
.include "System16_V02.asm" ; div. Utilities, z.B. ASCII_to_hex u.ä.
.include "MathLibSF24_16_V07.asm" ; 32-Bit Fliesskomma Bibliothek
.include "M2KNavigation_16_V08.asm" ; Koppelnavigations-Algorithmus
.include "ADNS2610SPI16_V05.asm" ; SPI-Kommunikation mit dem Maussensor
.include "ASADNS2610IF16_V04.asm" ; Interface zwischen Hauptprogramm und Maussensoren
.include "ZstAutomat16_V04.asm" ; der Zustandsautomat zum Hochfahren und Überwachen der Maussensoren usw.
Dieses Vorgehen führt zu etwas erhöhtem Zeitbedarf, weil man sich bestimmte Vorschriften setzen und sie einhalten muss. Z.B. werden bei mir alle verwendeten Register mit "push"- und "pop"-Anweisungen gesichert, damit ich nicht jedesmal nachgucken muss, welche Register verändert werden. Trotzdem hat das Verfahren sich sehr bewährt, weil es mir erlaubt, mit Assembler fast wie in einer Hochsprache Programme zu schreiben. -
Der Vollständigkeit halber habe ich im Anhang noch die Geschichte mit dem Zustandsautomaten zuendegeführt. Bin gespannt auf Dein nächstes "Lernprogramm". Als Takt für den Timer-Interrupt schlage ich vor, zunächst mit so 2 bis 5 Hz anzufangen; da kann man das Flackern noch sehen. Wenn dann alles fehlerfrei läuft, kannst Du's immer noch schneller machen.
Ciao,
mare_crisium
@oberallgeier,
ja, für diese Anwendung könnte ein endlicher Zustandsautomat ein gangbarer Weg sein. Natürlich nur, wenn er in Assembler programmiert ist, nicht in cäh ;-) !
mare_crisium
robo_wolf
07.02.2010, 17:50
Habe den Code zum Einlesen und Auswerten des an PinD0 befindenlicher Taste.
Einige Zeilen sind stark an mare_crisium´s "endlichen Zustandsautomaten" angelehnt.
Das Schalten der LED habe ich vorerst weggelassen.
;***** STK500 Lernprogramm Nr.4d
;*** Aufgabe: die Taste an PinD0 schaltet die zugeordnete LED auf dem STK500
;*** 1. Tastendruck: LEDs einschalten
;*** 2. Tastendruck: LEDs ausschalten
;*** eine Timerroutine liest den TastenStatus ein
;*** zum Entprellen - muss die TimerRoutine 3mal hintereinander den gleichen Wert
;*** einlesen um einen Tastzustand zu erkennen, Zst_Var 0x03(log 1) bzw. 0x00(log 0)
;*** Taste nicht gedrueckt >>> log 0 in Tast_Stat(r17)
;*** Taste gedrueckt >>> log 1 in Tast_Stat(r17)
;
;
.include "m8515def.inc"
.def Temp = r16 ; Temporary Register
.def Tast_Stat = r17 ; Tasten Status
.def Tast_Stat0 = r18
.def LED_Stat = r19 ; LED Status
.def Zst_Var = r20 ; Zustandsvariable - beinhaltet eine 2Bit-Zahl zur Tastzustandserkennung
.def TOGGLE_SPRR = r21 ; Sperrt die Ausfuehrung von TastenTest wenn, keine neuer TIMER1_OVL voran gegangen
;
; TimerWerte(bei 8MHz - ca. 0,2sec)
.equ Timerwert = -60 ; Timerschritte bis zum Überlauf(fuer Simulatortest)
;.equ Timerwert = -1562 ; Timerschritte bis zum Überlauf
;
;
.equ LED_PORT = PORTB ; LEDs
.equ LED_DDR = PORTB-1 ; DataDirectory fuer LEDs
.equ TAST_PORT= PORTD ; Tasten
.equ TAST_DDR = PORTD-1 ; DataDirectory fuer TastenEingang
.equ TAST_PIN = PORTD-2 ; TastenEingang
;
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
reti ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
rjmp TIMER1_OVF ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
;*****
RESET:
ldi temp,(1<<CS10) ; Taktfrequenz Vorteiler 1 gewaehlt (fuer Simaulatortest)
; ldi temp,(1<<CS10)|(1<<CS12) ; Taktfrequenz mit Vorteiler 1024 gewaehlt
out TCCR1B,temp
; Timer1 vorladen
ldi temp,HIGH(Timerwert)
out TCNT1H,temp
ldi temp,LOW(Timerwert)
out TCNT1L,temp
ldi temp,(1<<TOIE1) ; Timer1 Overflow aktivieren
out TIMSK,temp
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out TAST_DDR, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out TAST_PIN, temp ;STK500 schaltet gegen GND - Taste gedreuckt (Pin==0)
out TAST_PORT, temp ;PullUp an PortD einschalten
out LED_DDR,Temp ;PORTB als Ausgang
out LED_PORT, temp ;PORTB (LEDs) aus
clr Tast_Stat ;Tast_Stat auf def. Zustand
clr Zst_Var
sei ; globale Interruptfreigabe
MAIN:
sbrc TOGGLE_SPRR,0 ; kein TOGGLE, wenn Bit0 ==0
rcall TOGGLE
rjmp MAIN
TIMER1_OVF:
push TEMP ; TEMP auf Stack sichern
in TEMP,SREG ; SREG auf Stack sichern
push TEMP ;
in Tast_Stat0, PIND ; Tasten werden komplett eingelesen
com Tast_Stat0 ; invertiere
clt ; loesche T-Flag
SBRC Tast_Stat0,0 ; RegisterBit0 von TastStat ==0 ueberspringe / T-Flag wird nicht gesetzt
set ; ==1 setze T-Flag
;
; default wird -1 in temp eingetragen | springt bei nicht gesetzten T-Flag zu TastZst0
; bei gesetzten T-Flag wird +1 in temp eingetragen
;
ldi temp, 0x03 ; temp == "-1"
brtc TastZst0
ldi temp, 0x01 ; temp == "+1"
;
; TastZustand0
;
TastZst0:
cpi Zst_Var,0x00 ; vergleiche ob Zst_Var ==0
brne TastZst1 ; springe wenn ungleich
sbrs temp, 0x01 ; ueberspringe naechsten Befehl, wenn temp negativ(0x03) ist
add Zst_Var, temp ; addieren temp in Zst_Var
rjmp TastZst_Exit
;
; TastZustand1
;
TastZst1:
cpi Zst_Var, 0x01 ; vergleiche ob Zst_Var ==1
brne TastZst2
add Zst_Var, temp
rjmp TastZst_Exit
;
; TastZustand2
;
TastZst2:
cpi Zst_Var, 0x02 ; vergleiche ob Zst_Var ==2
brne TastZst3
add Zst_Var, temp
rjmp TastZst_Exit
;
; TastZustand3 ; Fehler abfangen Zst_Var darf nur Werte zwischen 0x00 und 0x03 haben
;
TastZst3:
cpi Zst_Var, 0x03 ; vergleiche ob Zst_Var ==3
brne TastZst_Error
sbrc temp, 0x01
add Zst_Var, temp
;
TastZst_Error:
;
TastZst_Exit:
andi Zst_Var, 0xFB ; eventuellen Uebertrag loeschen
;
cpi Zst_Var, 0x00 ; ueberspringe naechsten Befehl
brne Test2
cbr Tast_Stat,0x01 ; >>>>>Taste nicht gedrueckt
;
Test2:
cpi Zst_Var,0x03 ; ueberspringe naechsten Befehl
brne T0_OVL_Exit
sbr Tast_Stat,0x01 ; >>>>>Taste gedrueckt
;
T0_OVL_Exit:
; Timer1 vorladen
ldi temp,HIGH(Timerwert)
out TCNT1H,temp
ldi temp,LOW(Timerwert)
out TCNT1L,temp
;
sbr TOGGLE_SPRR,0 ; Status fuer TOGGLE 1 - TOGGLE / 0 - kein TOGGLE
pop TEMP ;
out SREG,TEMP ; SREG zurueckschreiben
pop TEMP ; TEMP zurueckschreiben
reti
TOGGLE:
cbr TOGGLE_SPRR,0 ; loesche Bit0, um TOGGLE bis zum naechsten TIMER1_OVF zu sperren
nop ; kommt spaeter
ret
Ganz zufrieden bin ich damit nicht.. :-(
Es tut zwar was es soll, ABER:
- es wurden 5 Register dafuer benoetigt (schaem)
- ich bin mir nicht sicher, ob alles in der TimerServiceRoutine drin sein sollte ???
(hatte mal gelesen, das die Programmunterbrechungen so kurz wie moeglich sein sollten) bei mir spielt sich da fast alles ab.
- gern haette ich die von mare_crisium aufgestellte Zustandstabelle benutzt, nur leider verstehe ich deren Funktion nicht richtig (wieder schaem)
(Da braeuchte ich noch mal ein bischen Starthilfe...generell das Benutzen von arrays und deren Auswertung)
- mit dem Konzentrieren auf eine Taste PinD0 in diesem Beispiel, seh ich momentan kein einfaches Einbinden der restlichen 7 Tasten
- vom eigendlichen Ziel "IO-Treiber" bin ich noch sehr weit weg
Besserwessi
07.02.2010, 18:57
Solange man nur eine ISR hat, und sonst nichts zeitkritisches läuft, macht es nicht wenn die ISR etwas länger ist. Wenn man weiss was man macht, kann man da auch längere Sachen machen, wie einen String per UART verschicken. Meine längste ISR braucht etwa 2 Sekunden - das Hauptprogramm macht dafür nichts außer sleep.
So lang ist die ISR auch noch gar nicht. da ist schließlich keine Schleife drin. An einigen Stellen läßt sich noch was feilen, aber das lernt man auch noch. Ein Beispiel was mit sehr umständlich vorkommt ist die Art wie das Bit0 vonder Taste in T flag verschoben wird. Dafür gibt es direkt den Befehl BST, den ich allerdings auch noch nie richtig benutzt habe.
Es ist auch keine Schande die Register auch zu benutzen. Solange die Register da sind kann man das machen, und kommt so ohne RAM aus. geht schließlich schneller und wird kürzer.
Man kann sogar soweit gehen, für die Interrupts eigene Register zu reservieren, so daß man keine Register mehr auf den Stack retten muß.
mare_crisium
09.02.2010, 11:00
robo_wolf,
wenn sich einer zu schämen hat, dann wohl ich, weil ich's nicht gut genug erklärt habe :oops: . Wenn's Dir nichts ausmacht, dann lass' uns die Sache in kleineren Schritten angehen (siehe Anhang).
Ciao,
mare_crisium
Edit 1:
im .pdf haben sich folgende Fehler eingeschlichen:
1. "...mit der „ldi2-Anweisung setzen kann." Gemeint ist die "ldi"-Anweisung.
2. Im Programm-Ausschnitt "; LEDs von Tasten 1 und 2 steuern" müssen die Skip-Anweisungen nicht "sbrs r19,bFLANKE0" und "sbrs r19,bFLANKE1" sondern "sbrc r19,bFLANKE0" und "sbrc r19,bFLANKE1" heissen.
robo_wolf
10.02.2010, 19:12
Hallo mare_crisium,
vielen Dank fuer die Erlaerungen.
Die fehlerhafte Vektortabelle hatte ich bisher nicht bemerkt.(Asche ueber mein Haupt)
Bis jetzt war bei Timer0 Schluss...und mit Timer1 ist es das erste Programm.
Aus diesem Grund sind mir die CTC-, Compare-Funktionen zwar bekannt, aber es fehlte die praktische Anwendung.
Du hast geschrieben, das die CTC-Funktion besser geignet sei.
Da habe ich nun einen praktischen Anwendungsfall :-)
CTC bedeutet also, dass er nach dem Erreichen des voreingestellten OCR1A und, oder OCR1B den Interrupt ausloest und die Serviceroutine durchlaeuft.
Es ist dann aber so, dass er beim Verlassen der Serviceroutine keinen definierten Zaehlerstand, je nach Verzweigung, hat? Oder liege ich da falsch?
Das spielt bei mir aber eine untergeordnete Rolle. Man kann auch sagen 100 Takte hin oder her sind bei der gewuenschten Funktion egal,
da keine Timings eingehalten werden muessen.
Es vereinfacht aber Programm an dieser Stelle, da das Nachladen entfaellt.
Die Funktionen des AVR-Studios hinsichtlich des Manipulieren(Setzen und Ruecksetzen von verschiedener PortBits oder Registerwerte)
sind mir schon bekannt und ich nutze sie.
Das zusaetzliche Anlegen der Daten fuer die Simulation hatte einen anderen Zweck.
Wenn ich bestimme wann die Programmunterbrechnung und Ausfuehrungen der ISR passieren, dann habe ich keine Willkuer vom Programm.
...will sagen, die Unterbrechungen passieren dann dort, wo ich sie nicht erwarte...
So hatte ich schon "kleine Anfaengerfehler" gefunden. War fuer mich eine zusaetzliche Ueberpruefung.
Trotzdem Danke fuer die Erlaeuterungen.
„BST“-Anweisung kannte ich bis jetzt nicht wirklich.
Du musst dir vorstellen, wenn ich ein programmtechnisches Problem loesen moechte,
dann nehme ich mir das Datasheet oder die AVR-Instuktionsliste zur Hand und probiere...
Auch muss ich wieder sagen, ... super habe nun eine praktische Anwendung dafuer
*/Ach, ich glaube, ich habe Dich auf zu komplizierte Gedanken gebracht. Tut mir leid, ich bin halt kein Pädagoge ;-) /*
Das ist schon OK so und bitte weiter so. Es schadet mir gewiss nicht, ueber das Geschriebene laenger nach zu denken.
Wenn es zu kompliziert wird, melde ich mich schon :-)
Nun habe ich leider nicht konkret geschrieben, was mir schwer faellt zu begreifen.. sorry
So kam es, wie es kommen musste.
Du hast genau, dass was mir klar war, noch einmal schoen beschrieben. - geschadet hat es mir ganz sicher nicht
- Du hattest nur Arbeit damit -
Den eigentlichen Ablauf habe ich schon verstanden und kann ihn nachvollziehen.
ABER ](*,)
>>> Die Sache mit den .dw-Anweisungen... da habert es ...
Die Werte die Du angegeben hast, sind von Dir (willkuerlich)festgelegt und beschreiben die Zustaende und belegen praktischer Weise 4Bit,
also ein Nibble eines Registers.
Da glaubte ich erst einen bestimmten Algoritmus, der dahinter steht, finden zu muessen.
- es sind leicht zu erkennende Zustaende, die nur irgendwie sinnvoll angeordnet sein muessen -
Dadurch lassen sich 2 Tasten in ein Register speichern.
Nun sind die Werte mit den .dw-Anweisungen festgelegt... und wo kommen die hin, sind sie abgelegt.?
>>> Danach hast Du Zeiger verwendet. ist ebenfalls Neuland fuer mich.(Hier liegt glaube ich mein groesstes Manko)
Wie der Name schon sagt, zeigen bzw. verweisen sie auf etwas. In dem Fall Speicher stellen.
>>> add und adc -Addieren mit und ohne Carry-Flag
>>> dann waere da noch "lpm" lesen aus dem Programmspeicher
Wahrscheinlich, dessen bin ich mir sicher, sind das ganz banale Dinge... die ich mir komplizierter denke, als sie in Wirklichkeit sind.
Das sind bei weitem nicht alle Fragen, die ich gern los werden moechte :-)
Aber mit den Antworten, kaeme ich erst einmal wieder ein Schritt weiter.
Ich hoffe, dass ich Dich nicht zu sehr damit quaele?
Du hast auf jeden Fall einen dankbaren Schueler... auch wenn Du kein Paedagoge bist.
Es ist eben nicht so einfach, etwas zu erklaeren, wenn man nicht genau weis, wie der andere(ich) es versteht.
mare_crisium
11.02.2010, 12:10
robo_wolf,
nur immer zu mit Fragen :-). Dafür ist dieser Fred ja da.
".dw"-Anweisung:
Mit der „.dw“-Anweisung wird ein Wort, also ein 16-Bit-Wert, in den Programmspeicher geschrieben. Die Syntax des AVR-Assemblers erlaubt es, auch gleich mehrere Worte auf einmal zu schreiben. Z.B. ist
...
TABELLE:
.dw 0xABCD,0x1234,0x5678
...
ebenfalls erlaubt. Man kann die Einträge 0xABCD usw. wiederfinden, wenn man das Memory-Fenster des Simulator auf „Program“ einstellt und sich die Speicherinhalte ansieht, die auf die Adresse des Labels „TABELLE“ folgen (die Adresse von „TABELLE“ sieht man, wenn man im Simulator im Quelltext den Mauszeiger auf das Label stellt). Angenommen, die Adresse von „TABELLE“ sei 0x0046. Dann sieht es im Programmspeicher so aus
0046 CD AB 34 12
004A 78 56 ...
Das LSB wird also immer in der niedrigeren, geradzahligen Adresse abgelegt, danach erst das MSB. Man nennt diese Speichermethode auch „little endian“, im Gegensatz zum „big endian“, wo das MSB an der niedrigeren Adresse steht.
Die Daten, die auf die Adresse „TABELLE“ folgen, kann man mit der „lpm“-Anweisung auslesen. Die Einzelheiten des Befehls sind in der Assembler-Hilfe des AVR-Studios gut beschrieben.
Die Tastenzustände ändern sich laufend und müssen deshalb in den Registern oder im RAM-Speicher gehalten werden; der Programmspeicher soll zur Laufzeit des Entprellprogramms nicht mehr geändert werden. Deshalb dienen die „.dw“-Anweisungen ausschliesslich dazu, im Programmspeicher die Übergangstabelle zu erzeugen. Mit den aktuellen Tastenzuständen haben sie nichts zu tun.
Timer/Counter1:
In der Betriebsart CTC springt der Timer wieder auf Null zurück, nachdem er den Wert von OCR1A bzw. OCR1B erreicht hat, und zählt dann munter weiter. Dadurch bleibt das Zeitintervall zwischen zwei Interrupts immer dasselbe. Wo wie Du es gemacht hattest, beginnt die Zählung vom Rücksetzen des Zählerstandes neu. Er ist also von der Ausführungsdauer des Interrupt-Dienstprogramms abhängig.
Ich nehme an, Du hast das so gemacht, weil Du befürchtetest, der nächste Interrupt könnte eintreten, bevor das Dienstprogramm beendet ist. Probier' mal im Simulator, was passiert, wenn Du den CTC-Interrupt von TC1 manuell auslöst, während das Dienstprogramm noch läuft... Wenn Du solche Ereignisse partout vermeiden willst, dann hilft der Vergleich von PreScaler*OCR1A_Wert mit der maximalen Anzahl Taktzyklen im Dienstprogramm. Solange PreScaler*OCR1A > max.TaktZyklen ist, fällt der Interrupt immer in die Zeit nach dem Ende des Dienstprogramms.
Ciao,
mare_crisium
robo_wolf
11.02.2010, 19:24
Hallo mare crisium,
schnelle und praezise Erklaerung.
Das bringt doch gleich etwas LICHT ins dunkel.
versteh ich das richtig?
von 0xABCD
LSB ist "CD"
und
MSB ist "AB"
Habe mal bei mir den Quellcode eingefuegt und mir den Programmspeicher angeschaut:
TABELLE:
.dw 0xABCD,0x1234,0x5678
Label Tabelle: zeigt auf die Speicherzelle 0x0019(16Bit breit) und
.dw 0xABCD,0x1234,0x5678 sind bei
0x0019 CD AB
0x001A 34 12
0x001B 78 56 abgelegt
also zuerst LSB und dann MSB in der Adresse
Nun hast Du geschrieben:
"Das LSB wird also immer in der niedrigeren, geradzahligen Adresse abgelegt, danach erst das MSB. "
??? 0x0019 und 0x001B sind doch keine geradzahligen Adressen ???
Ich glaub, ich steh hier schon wieder auf der Leitung.
mare_crisium
11.02.2010, 22:22
Guten Abend, robo_wolf!
Ich glaub, ich steh hier schon wieder auf der Leitung.
:-) nee, stehst Du nicht: Du bist nur über die Gemeinheiten der Speicherorganisation bei ATMELs MCs gestolpert.
Guck' mal:
0x0019 CD AB
Offenbar ist im Flash-Speicher unter jeder Adresse je ein 2-Byte-Wort gespeichert.
Deshalb ist der Programmzähler (PC) so aufgebaut, dass PC+1 auf das nächste 2-Byte-Wort nach PC zeigt. Beim Inkrementieren springt der PC um zwei Bytes weiter. So sind auch die Adresse 0x0019 aus Deinem Beispiel zu verstehen: Es ist die Adresse eines 2-Byte-Wortes.
Im Gegensatz dazu ist im RAM unter jeder Adresse je ein 1-Byte-Wort gespeichert.
Deshalb sind die Zeigerregister zh:zl, yh:yl und xh:xl so aufgebaut, dass sie beim Inkrementieren um ein 1-Byte-Wort weiterspringen.
Beim Auslesen des Flash-Speichers mit der „lpm“-Anweisung muss man zh:zl als Zeigerregister verwenden. Das führt zu Schwierigkeiten: zh:zl kann nur auf einzelne Bytes zeigen, die Flashspeicher-Adressen beziehen sich aber immer auf ein 2-Byte-Wort. Also muss man die Flashadresse in zh:zl verdoppeln, dann zeigt zh:zl auf das LSB des gespeicherten Doppelbytes.
Beispiel: Das MSB des unter 0x0019 im Flash gespeicherten Wortes soll ausgelesen werden
ldi zh,HIGH(0x0019)
ldi zl,LOW(0x0019)
add zl,zl ; Zeiger verdoppeln
adc zh,zh ; jetzt zeigt er auf das LSB
; lpm r16,z ; diese Anweisung würde jetzt das LSB (0xCD) nach r16 befördern
adiw zh:zl,0x0001 ; wir wollen ans MSB kommen, also noch eins draufaddieren
lpm r16,z ; so, jetzt landet das MSB (0xAB) in r16.
Du hast also recht: Der PC zeigt auf gerade wie ungerade Adressen im Flashspeicher. Ich bezog mich auf die Verwendung von zh:zl im Flashspeicher. Da steht das LSB immer unter der geraden Adresse und das MSB unter der ungeraden.
Am Besten, Du probierst's direkt im Simulator und mit einem Glas Wein gleich 'mal aus ;-) .
Ciao,
mare_crisium
robo_wolf
14.02.2010, 15:44
Hallo mare_crisium,
leider habe ich noch immer Verstaendnisprobleme.
- Das Lesen der Tabellenwerte mit Zeigern und deren Verarbeitung -
:-k
Ich habe Deinen Code mit einfliessen lassen und habe ihn nun so einge Male durch den Simulator getickert.
Aber es hat leider bei mir noch immer nicht klick gemacht... :-s
Selbst mit den Wein nicht.
Den Code dokumentierten Zeile haenge ich diesmal an.
Dokumentiert sind 3 Durchlaeufe.
Vielleicht kannst Du mir noch ein paar Erlaeuterungen ergaenzen ?
mare_crisium
14.02.2010, 18:38
robo_wolf,
nicht locker lassen! Das mit der Tabelle konnte nicht klappen, weil Du zh:zl nicht mit dem Zeiger auf den Tabellenanfang geladen hattest ;-) .
Versuch' mal das hier:
;***** STK500 Lernprogramm Nr.4e
;*** Aufgabe: die Taste an PinD0 schaltet die zugeordnete LED auf dem STK500
;*** 1. Tastendruck: LEDs einschalten
;*** 2. Tastendruck: LEDs ausschalten
;*** eine Timerroutine liest den TastenStatus ein
;*** zum Entprellen - muss die TimerRoutine 3mal hintereinander den gleichen Wert,
;*** einlesen um einen Tastzustand zu erkennen, Zst_Var 0x03 bzw. 0x00
;*** Taste nicht gedrueckt >>> log 0 in Tast_Stat(r17)
;*** Taste gedrueckt >>> log 1 in Tast_Stat(r17)
;
;
.include "m8515def.inc"
.def Temp = r16 ; Temporary Register
.def Tast_Stat = r17 ; Tasten Status
.def Tast_Stat0 = r18
.def LED_Stat = r19 ; LED Status
.def Zst_Var = r20 ; Zustandsvariable - beinhaltet eine 2Bit-Zahl zur Tastzustandserkennung
.def LED_Update_SPRR = r21 ; Sperrt die Ausfuehrung von TastenTest wenn, keine neuer TIMER1_OVL voran gegangen
;
.equ Timerwert = 255 ;-1562 ; Timerschritte bis zum Überlauf
.equ LED_PORT = PORTB ; LEDs
.equ LED_DDR = PORTB-1 ; DataDirectory fuer LEDs
.equ TAST_PORT= PORTD ; Tasten
.equ TAST_DDR = PORTD-1 ; DataDirectory fuer TastenEingang
.equ TAST_PIN = PORTD-2 ; TastenEingang
; Bit-Definitionen für Z-Wert
.equ bZWERT_FLANKE = 3 ; Bit3 speichert das "Flankenbit"
; 0 = "keine Flanke"
; 1 = "Flanke steht an"
.equ bZWERT_BETAETIGT = 0 ; Bit0 speichert den Tastenzustand
; 0 = "nicht betätigt"
; 1 = "betätigt"
; Pinzuordnung für div. Tasten
.equ TASTE0_PIN = 0
.equ TASTE1_PIN = 1
.equ TASTE2_PIN = 3 ; die Zuordnung der PINs muss ja nicht mit der TastenNr übereinstimmen!
.equ TASTE3_PIN = 2
; jetzt kommen die Register, die die Z-Werte der Tasten enthalten. Im Prinzip
; braucht man nur jeweils nur ein Nibble pro Taste. Um die Sache erstmal einfach
; zu halten wird für jede Taste ein ganzes Byte verbraucht.
.def rTASTE0_ZST = r15 ; enthält Zustand Taste0
.def rTASTE1_ZST = r14 ; enthält Zustand Taste1
.def rTASTE2_ZST = r13 ; enthält Zustand Taste2
.def rTASTE3_ZST = r12 ; enthält Zustand Taste3
;
; Vektortabelle
;
rjmp RESET ; 1 - $000 RESET External Pin, Power-on Reset, Brown-out, Reset and Watchdog Reset
reti ; 2 - $001 INT0 External Interrupt Request 0
reti ; 3 - $002 INT1 External Interrupt Request 1
reti ; 4 - $003 TIMER1 CAPT Timer/Counter1 Capture Event
rjmp TIMER1_COMPA_ISR ; 5 - $004 TIMER1 COMPA Timer/Counter1 Compare Match A
reti ; 6 - $005 TIMER1 COMPB Timer/Counter1 Compare Match B
reti ; 7 - $006 TIMER1 OVF Timer/Counter1 Overflow
reti ; 8 - $007 TIMER0 OVF Timer/Counter0 Overflow
reti ; 9 - $008 SPI, STC Serial Transfer Complete
reti ; 10 - $009 USART, RXC USART, Rx Complete
reti ; 11 - $00A USART, UDRE USART Data Register Empty
reti ; 12 - $00B USART, TXC USART, Tx Complete
reti ; 13 - $00C ANA_COMP Analog Comparator
reti ; 14 - $00D INT2 External Interrupt Request 2
reti ; 15 - $00E TIMER0 COMP Timer/Counter0 Compare Match
reti ; 16 - $00F EE_RDY EEPROM Ready
reti ; 17 - $010 SPM_RDY Store Program memory Ready
;
TBL_UEBERGANG_01:
.dw 0x0008 ; Tabellenlänge
; T = 0 T = 1
.dw 0x0200 ; Z=0 -> Z=0,Flanke=0 / Z=2,Flanke=0
.dw 0x0308 ; Z=1 -> Z=0,Flanke=1 / Z=3,Flanke=0
.dw 0x0400 ; Z=2 -> Z=0,Flanke=0 / Z=4,Flanke=0
.dw 0x0501 ; Z=3 -> Z=1,Flanke=0 / Z=5,Flanke=0
.dw 0x0602 ; Z=4 -> Z=2,Flanke=0 / Z=6,Flanke=0
.dw 0x0703 ; Z=5 -> Z=3,Flanke=0 / Z=7,Flanke=0
.dw 0x0F04 ; Z=6 -> Z=4,Flanke=0 / Z=7,Flanke=1
.dw 0x0705 ; Z=7 -> Z=5,Flanke=0 / Z=7,Flanke=0
; verweist auf den Programmspeicher und zeigt dort im ersten Wort auf den ersten
; definierten Wert(Tabellenlaenge),
; anschliessende Worte beinhalten die festgelegten Werte fortlaufend
; bedeutet in meinem Fall Verweis auf 0x0011 mit 08 00 (.dw 0x0008)
; 0x0012 mit 02 00 (.dw 0x0200)
; 0x0013 mit 00 00 (.dw 0x0000)
; 0x0014 mit 04 00 (.dw 0x0400)
; 0x0015 mit 08 50 (.dw 0x0580)
; 0x0016 mit 02 87 (.dw 0x8702)
; 0x0017 mit 03 07 (.dw 0x0703)
; 0x0018 mit 00 00 (.dw 0x0000)
; 0x0019 mit 05 07 (.dw 0x0705)
; ->>>
; Der Wert(Tabellenlaenge) ist auch nur ein festgelegter Wert und fuer Programmoperationen wichtig.
;
RESET:
;
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
;
ldi temp,(1<<CS10)|(1<<WGM12) ; Taktfrequenz Vorteiler 1 gewaehlt (fuer Simaulatortest)
; ldi temp,(1<<CS10)|(1<<CS12)|(1<<WGM12) ; Taktfrequenz mit Vorteiler 1024 gewaehlt
out TCCR1B,temp
;
ldi temp,HIGH(Timerwert)
out OCR1AH,temp
ldi temp,LOW(Timerwert)
out OCR1AL,temp
;
ldi temp,(1<<COM1A1)|(0<<COM1A0)
out TCCR1A,temp
ldi temp,(1<<OCIE1A) ; Timer1 Overflow aktivieren
out TIMSK,temp
;
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out TAST_DDR, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out TAST_PIN, temp ;STK500 schaltet gegen GND - Taste gedreuckt (Pin==0)
out TAST_PORT, temp ;PullUp an PortD einschalten
out LED_DDR,Temp ;PORTB als Ausgang
out LED_PORT, temp ;PORTB (LEDs) aus
; Tastenzustände aller Tasten auf Null setzen
clr r16 ;
mov rTASTE0_ZST,r16 ; Taste0 und Taste1 zurücksetzen
mov rTASTE2_ZST,r16 ; Taste2 und Taste3 zurücksetzen
clr Zst_Var
;
sei
;
;
MAIN:
sbrc LED_Update_SPRR,0 ; kein LED_Update, wenn Bit0 ==0
rcall LED_Update
rjmp MAIN
;
LED_Update:
cbr LED_Update_SPRR,0 ; loesche Bit0, um LED_Update bis zur naechsten TIMER1_ISR zu sperren
ret
;
/*-------------------------------------
INTERRUPTDIENST TIMER1_COMPA_ISR
Die Interruptdienstprogramm TIMER1_COMPA_ISR wird vom Output-Match-Interrupt von
Timer1 ausgelöst. Es liest die aktuellen Messwerte der Tasten vom TAST_PIN ab und
berechnet für jede Taste den neuen Zustand (Z-Wert).
Eingansgrössen:
keine
Ausgangsgrössen:
keine
geänderte Register
keine
Anzahl Zyklen
*/
TIMER1_COMPA_ISR:
push r25
in r25,SREG
push r16
push r17
push r18
push zl
push zh
/*
Das hier hattest Du übersehen:
Der Zeiger auf die Übergangstabelle muss VOR (!!) dem Aufruf von NEXT0_TAST_ZST
in die Register zh:zl geladen werden !!
*/
ldi zl,LOW(TBL_UEBERGANG_01) ; zh:zl := Zeiger auf Übergangstabelle ...
ldi zh,HIGH(TBL_UEBERGANG_01) ; ...wird für NEXT0_TAST_ZST gebraucht
; Betätigungszustand der Tasten einlesen und der Reihe nach verarbeiten
in r18,TAST_PIN ; r18 := Messwerte aller Pins
; Taste0 verarbeiten
bst r18,TASTE0_PIN ; überträgt den Messwert von TASTE0_PIN ins T-Flag von SREG
mov r16,rTASTE0_ZST ; r16 := Z-Wert Taste0
andi r16,0x07 ; "Flankenbit" löschen
rcall NEXT0_TAST_ZST ; Folgezustand für Taste0 in r16 berechnen
mov rTASTE0_ZST,r16 ; neuen Z-Wert in TASTE0_ZST speichern
; Taste1 verarbeiten
bst r18,TASTE1_PIN ; überträgt den Messwert von TASTE1_PIN ins T-Flag von SREG
mov r16,rTASTE1_ZST ; r16 := Z-Wert Taste1
andi r16,0x07 ; "Flankenbit" löschen
rcall NEXT0_TAST_ZST ; Folgezustand für Taste1 in r16 berechnen
mov rTASTE1_ZST,r16 ; neuen Z-Wert in TASTE1_ZST speichern
; usw...
sbr LED_Update_SPRR,0 ; Status fuer LED_Update 1 - LED_Update / 0 - kein LED_Update
pop zh
pop zl
pop r18
pop r17
pop r16
out SREG,r25
pop r25
reti
;
/*-------------------------------------
PROZEDUR NEXT0_TAST_ZST
Die Prozedur NEXT0_TAST_ZST liest den Folgezustand der Tastenentprellung aus einer
Übergangstabelle aus. Die Adresse der Tabelle wird in zh:zl übergeben. Der aktuelle
Zustand (Z-Wert) wird in r16 übergeben. Der aktuelle Tastenzustand (betätigt/nicht
betätigt) ist im T-Flag von SREG gespeichert. Das Ergebnis wird in r16 zurückgegeben.
Die Tabelle, darf maximal 127 Einträge enthalten. Der erste Eintrag der Tabelle ist
die Länge der Tabelle und dient der Bereichsüberprüfung.
D.h. der Zeiger auf die Übergangstabelle muss zh:zl geladen werden BEVOR diese
Prozedur aufgerufen wird!! In diesem Beispiel, bei dem nur eine einzige Übergangstabelle
im Spiel ist, könnte man zh:zl auch innerhalb von NEXT0_TAST_ZST laden. Um die
Prozedur auch in Programmen verwenden zu können, in denen mehrere Zustandsautomaten
nebeneinanderherlaufen, ist es sinnvoll, den Zeiger vorher zu laden. Dann kann je
nach Aufgabe eine andere Tabelle verwendet werden. Kommt in meinen Programmen öfters
vor ;-).
Eingansgrössen:
r16 : enthält aktuellen Z-Wert der Taste
zh:zl : enthält Zeiger auf die Übergangstabelle
SREG (T-Flag) : enthält den aktuellen Messwert der Taste
T = 0 : Taste nicht betätigt
T = 1 : Taste betätigt
Ausgangsgrössen:
r16 : enthält den neuen Z-Wert für die Taste
zh:zl unverändert
SREG unverändert
geänderte Register
r16
Anzahl Zyklen
*/
NEXT0_TAST_ZST:
push r17
in r17,SREG
push r17
push r18
push zl
push zh
; Zeiger auf Anfang der Übergangstabelle berechnen
add zl,zl ; zh:zl verdoppeln, weil einer Adresse im...
adc zh,zh ; .. Programmspeicher immer zwei Bytes entsprechen
; FRAGE: Wo wird die Information abgelegt -> im Flashspeicher
;
;
; Information in Bit7 löschen; Tabelle darf nicht länger als 127 Einträge sein
andi r16,0x7F ;
; Tabellenlänge einlesen (0-ter Eintrag in der Tabelle)
lpm r17,z+ ; r17 := TBL_LNG einlesen
; 1.Durchgang
; es wird in R16 der Wert 0x19 eingetragen - ??? Die letzte Stelle in der Tabelle (0x0019)
; und in R30(ZL) die 0x01
;
; 2.Durchgang
; es wird in R16 der Wert 0x19 eingetragen - ??? Die letzte Stelle in der Tabelle (0x0019)
; und in R30(ZL) die 0x01
;
; KLAR; Du hattest den Zeiger nicht gesetzt!
;
;
lpm r18,z+ ; zweites Byte überlesen, wird nicht gebraucht
; Bereichsprüfung ( r17 < TBL_LNG )
cp r16,r17 ; Tabellenindex mit TBL_LNG vergleichen
brlt NXT0ZST_00 ; Sprung, wenn Tabellenindex iO
; Fehler: Tabellenindex ist zu gross
clr r16 ; Zustand auf Null setzen
rjmp NXT0ZST_EXIT ; fertig
NXT0ZST_00:
; Tabellenindex im zulässigen Bereich
clr r17 ;
add zl,r16 ; Index zweimal zum Zeiger addieren, weil...
adc zh,r17 ; ... in der Tabelle...
add zl,r16 ; ... 2-Byte-Worte gespeichert sind.
adc zh,r17 ; zh:zl := Zeiger auf Tabelleneintrag
lpm r16,z+ ; r16 := Folgezustand für T=0
lpm r17,z ; r17 := Folgezustand für T=1
brtc NXT0ZST_EXIT ; fertig, wenn T = 0
; T = 1, d.h. Taste betätigt
mov r16,r17 ; zweiten Eintrag als Folgezustand verwenden
NXT0ZST_EXIT:
; sbr LED_Update_SPRR,0 ; Status fuer LED_Update 1 - LED_Update / 0 - kein LED_Update
; diese Zeile gehört in den Interruptdienst!
pop zh
pop zl
pop r18
pop r17
out SREG,r17
pop r17
ret
;
Diese Version hat eine etwas geänderte Übergangstabelle. Sie macht es möglich, anhand der Bits "bZWERT_FLANKE" und "bZWERT_BETAETIGT" auf einfache Weise den aktuellen Zustand der Taste abzufragen.
Macht Spass mit Dir durch die Geschichte durchzukriechen :-) !
Ciao,
mare_crisium
Edit: P.S.: Könnte es sein, dass Dir meine Art der Parameterübergabe an die Prozedur NEXT0_TAST_ZST fremd ist? Die Register zh:zl, r16 und das T-Flag werwende ich wie die Parameter bei einem Prozeduraufruf in einer Hochsprache, z.B. Pascal:
procedure NEXT0_TAST_ZST( var Taste_Zst : byte;
Tabelle : Pointer;
betaetigt : boolean );
Hier ist zugeordnet
Taste_Zst -> r16, Tabelle -> zh:zl, betaetigt -> T-Flag in SREG
robo_wolf
15.02.2010, 20:04
Hallo mare_crisium,
Daaaanke und schon die naechste Frage, die ich mir hoffentlich selbst beantwortet habe.
Meine Anmerkungen bzw. Gedanken dazu sind im Codesegment.
NEXT0_TAST_ZST:
push r17
in r17,SREG
push r17
push r18
push zl
push zh
// Bis hier hin kann ich es nachvollziehen. Aber nun wird der Eintrag der 0.Stelle von 0x0011 auf 0x0022 berechnet.
// ...und was das schlimmste ist, hinterher auch noch richtig ausgelesen... :-()
// Bei 0x0022 sind doch ganz andere Daten abgelegt, keine aus der Tabelle. Die enden bei 0x0019.
// >>>Glaube, Du hast es mir schon einmal versucht in "Breiform" zu servieren? :-)
; Zeiger auf Anfang der Übergangstabelle berechnen
add zl,zl ; zh:zl verdoppeln, weil einer Adresse im...
adc zh,zh ; .. Programmspeicher immer zwei Bytes entsprechen
// Wenn ich den Kommentar von Dir nun richtig interpretiere, wird auf den Speicher 0x0011 - 0x0019 verwiesen und dort stehen die Daten zu je 16Bit.
// Zumindest sehe ich sie so im Simulator. Dort kann ich sie aber nicht wirklich ansprechen, da der ATmega8515 ein 8Bit-Prozessor ist und somit auch nur
// 8Bit mit einmal verarbeiten kann.
// Bedeutet das die 0. Stelle der Tabelle zwar bei 0x0011 abgelegt, ist aber fuer die CPU bei 0x0022 und 0x0023
// 1. Stelle der Tabelle zwar bei 0x0012 abgelegt, ist aber fuer die CPU bei 0x0024 und 0x0025
// 8. Stelle der Tabelle zwar bei 0x0019 abgelegt, ist aber fuer die CPU bei 0x0032 und 0x0033
// "gerade" und "ungerade"
// Ist es so??? Wenn ja, dann habe ich es doch verstanden. ...jedoch nicht warum Atmel keine 8Bit Speicherstruktur verwendet.
;
;
; Information in Bit7 löschen; Tabelle darf nicht länger als 127 Einträge sein
andi r16,0x7F ;
; Tabellenlänge einlesen (0-ter Eintrag in der Tabelle)
lpm r17,z+ ; r17 := TBL_LNG einlesen
// Hier wird der Tabellenwert eingelesen und der Zeiger von 0x0022 auf die 2. "ungerade" Stelle 0x0023 addiert
lpm r18,z+ ; r18 := TBL_LNG einlesen
// Hier wird der Tabellenwert eingelesen und der Zeiger von 0x0023 auf die 2. "gerade" Stelle 0x0024 addiert - den naechsten Tabelleneintrag
//
// >>>Habe ich den "Brei" nun mit dem Laeufel oder mit der Gabel gegessen?
//
Edit: P.S.: Könnte es sein, dass Dir meine Art der Parameterübergabe an die Prozedur NEXT0_TAST_ZST fremd ist?
Die Register zh:zl, r16 und das T-Flag werwende ich wie die Parameter bei einem Prozeduraufruf in einer Hochsprache, z.B. Pascal:
Fremd? ganz klar... ;-) darueber musst Du aber nicht nachdenken. Wenn ich sie dann einmal verstanden habe.. ist sie nicht mehr fremd.
Ich versuche an die Sache mit gesunden Menschenverstand ran zu gehen.
Manchmal brauche ich vielleicht etwas laenger, um komplett hinter jedes Problem zukommen.
Moechte aber auf keinen Fall Dinge, die ich beim ersten mal nicht gleich verstanden habe, ignorieren bzw. hinnehmen.
Das waere Selbstbetrug und holt mich nur all zu schnell wieder ein.
- Es ist ein Hobby und es macht Spass mit nur "1" und "0" zu spielen -
Was ich damit sagen will:
Es ist voll OK so. und bin wirklich froh, dass Du mich beim Lernen so tatkraeftig unterstuetzt.
mare_crisium
16.02.2010, 08:32
robo_wolf,
genau das
... da der ATmega8515 ein 8Bit-Prozessor ist und somit auch nur 8 Bit mit einmal verarbeiten kann.
ist der springende Punkt ;-). Und das kommt so: Es gibt zwei gängige Arten von Computer-Architektur, die von-Neumann- und die Harvard-Architektur (In Wikipedia sind die beiden Architekturen ganz gut erläutert).
Bei der v-Neumann-Variante werden Programm- und Datenspeicher über denselben Bus angesprochen. Klar, dass dann die Maschinenbefehle und die Daten im einfachsten Fall dieselbe Anzahl Bits haben. Beispiel: Die alten 8048 bis 8085, 6502, 6800 und Z80 usw. hatten alle eine von-Neumann-Struktur und einen Daten- und Programmbus mit 8Bit Breite. Wenn längere Maschinenbefehle nötig waren, mussten eben zwei Bytes eingelesen werden. Die 8086- und die 68000-Reihe waren auch vom von-Neumann Typ, aber mit 16 Bit-Bus.
Bei der Harvard-Architektur sind Daten- und Programmspeicher voneinander getrennt, und es gibt separate Busse für Daten und Programm. Der Vorteil ist, dass man Programmbefehle und Daten gleichzeitig einlesen kann. Auch können die Maschinenbefehle mehr Bits haben als ein Datenwort - dadurch kann man auch längere Maschinenbefehle in nur einem Speicherzugriff einlesen. Beispiel: Die ATmega-Prozessoren.
Also: Der Programmspeicher bei den ATmega-MCs ist in 16-Bit-Worten organisiert. Die Adresse, die einem Label zugeordnet wird, zeigt immer auf den Anfang eines Doppelbytes. Die "lpm"-Anweisung behandelt den Programmspeicher aber so, als wäre er in 8-Bit-Worten organisiert. Deshalb muss man die Labeladressen in zh:zl verdoppeln, um an die richtigen Daten zu kommen. Na? ;-) !
Ciao,
mare_crisium
robo_wolf
17.02.2010, 17:15
Hallo mare_crisium,
dann werde ich das Programm mal komplettieren, 2 Zustaende in ein Register unterbringen und in mein naechsten Post setzen.
robo_wolf
19.02.2010, 07:26
Hallo mare_crisium,
wollte eigentlich mit diesem Post das fertige Programm praesentieren.
Leider bin ich da noch auf ein seltsames Problem gestossen:
Ich habe deshalb den Code von Dir, ohne meine Erweiterungen, im Simulator laufen lassen.(Nur Deine Kommentare asm-konform veraendert)
Beim 2. Anspringen der ISR(Simulator steht in Zeile 62 "rjmp TIMER1_COMPA_ISR") wird der Eingang PinD5 von High auf Low umgeschaltet.
edit: Stimmt nicht ganz, das Umschalten passiert schon vorher bei Counter 269 - bevor die ISR angsprungen wird. Genau bei Ueberlauf des Timers auf 1.
Leider kann ich mir nicht erklaeren, wie es dazu kommt...
Koenntest Du den Code versuchsweise in Deinem Simulator laufen lassen?
;***** STK500 Lernprogramm Nr.4e
;*** Aufgabe: die Taste an PinD0 schaltet die zugeordnete LED auf dem STK500
;*** 1. Tastendruck: LEDs einschalten
;*** 2. Tastendruck: LEDs ausschalten
;*** eine Timerroutine liest den TastenStatus ein
;*** zum Entprellen - muss die TimerRoutine 3mal hintereinander den gleichen Wert,
;*** einlesen um einen Tastzustand zu erkennen, Zst_Var 0x03 bzw. 0x00
;*** Taste nicht gedrueckt >>> log 0 in Tast_Stat(r17)
;*** Taste gedrueckt >>> log 1 in Tast_Stat(r17)
;
;
.include "m8515def.inc"
.def Temp = r16 ; Temporary Register
.def Tast_Stat = r17 ; Tasten Status
.def Tast_Stat0 = r18
.def LED_Stat = r19 ; LED Status
.def Zst_Var = r20 ; Zustandsvariable - beinhaltet eine 2Bit-Zahl zur Tastzustandserkennung
.def LED_Update_SPRR = r21 ; Sperrt die Ausfuehrung von TastenTest wenn, keine neuer TIMER1_OVL voran gegangen
;
.equ Timerwert = 255 ;-1562 ; Timerschritte bis zum Überlauf
.equ LED_PORT = PORTB ; LEDs
.equ LED_DDR = PORTB-1 ; DataDirectory fuer LEDs
.equ TAST_PORT= PORTD ; Tasten
.equ TAST_DDR = PORTD-1 ; DataDirectory fuer TastenEingang
.equ TAST_PIN = PORTD-2 ; TastenEingang
; Bit-Definitionen für Z-Wert
.equ bZWERT_FLANKE = 3 ; Bit3 speichert das "Flankenbit"
; 0 = "keine Flanke"
; 1 = "Flanke steht an"
.equ bZWERT_BETAETIGT = 0 ; Bit0 speichert den Tastenzustand
; 0 = "nicht betätigt"
; 1 = "betätigt"
; Pinzuordnung für div. Tasten
.equ TASTE0_PIN = 0
.equ TASTE1_PIN = 1
.equ TASTE2_PIN = 3 ; die Zuordnung der PINs muss ja nicht mit der TastenNr übereinstimmen!
.equ TASTE3_PIN = 2
; jetzt kommen die Register, die die Z-Werte der Tasten enthalten. Im Prinzip
; braucht man nur jeweils nur ein Nibble pro Taste. Um die Sache erstmal einfach
; zu halten wird für jede Taste ein ganzes Byte verbraucht.
.def rTASTE0_ZST = r15 ; enthält Zustand Taste0
.def rTASTE1_ZST = r14 ; enthält Zustand Taste1
.def rTASTE2_ZST = r13 ; enthält Zustand Taste2
.def rTASTE3_ZST = r12 ; enthält Zustand Taste3
;
; Vektortabelle
;
rjmp RESET ; 1 - $000 RESET External Pin, Power-on Reset, Brown-out, Reset and Watchdog Reset
reti ; 2 - $001 INT0 External Interrupt Request 0
reti ; 3 - $002 INT1 External Interrupt Request 1
reti ; 4 - $003 TIMER1 CAPT Timer/Counter1 Capture Event
rjmp TIMER1_COMPA_ISR ; 5 - $004 TIMER1 COMPA Timer/Counter1 Compare Match A
reti ; 6 - $005 TIMER1 COMPB Timer/Counter1 Compare Match B
reti ; 7 - $006 TIMER1 OVF Timer/Counter1 Overflow
reti ; 8 - $007 TIMER0 OVF Timer/Counter0 Overflow
reti ; 9 - $008 SPI, STC Serial Transfer Complete
reti ; 10 - $009 USART, RXC USART, Rx Complete
reti ; 11 - $00A USART, UDRE USART Data Register Empty
reti ; 12 - $00B USART, TXC USART, Tx Complete
reti ; 13 - $00C ANA_COMP Analog Comparator
reti ; 14 - $00D INT2 External Interrupt Request 2
reti ; 15 - $00E TIMER0 COMP Timer/Counter0 Compare Match
reti ; 16 - $00F EE_RDY EEPROM Ready
reti ; 17 - $010 SPM_RDY Store Program memory Ready
;
TBL_UEBERGANG_01:
.dw 0x0008 ; Tabellenlänge
; T = 0 T = 1
.dw 0x0200 ; Z=0 -> Z=0,Flanke=0 / Z=2,Flanke=0
.dw 0x0308 ; Z=1 -> Z=0,Flanke=1 / Z=3,Flanke=0
.dw 0x0400 ; Z=2 -> Z=0,Flanke=0 / Z=4,Flanke=0
.dw 0x0501 ; Z=3 -> Z=1,Flanke=0 / Z=5,Flanke=0
.dw 0x0602 ; Z=4 -> Z=2,Flanke=0 / Z=6,Flanke=0
.dw 0x0703 ; Z=5 -> Z=3,Flanke=0 / Z=7,Flanke=0
.dw 0x0F04 ; Z=6 -> Z=4,Flanke=0 / Z=7,Flanke=1
.dw 0x0705 ; Z=7 -> Z=5,Flanke=0 / Z=7,Flanke=0
; verweist auf den Programmspeicher und zeigt dort im ersten Wort auf den ersten
; definierten Wert(Tabellenlaenge),
; anschliessende Worte beinhalten die festgelegten Werte fortlaufend
; bedeutet in meinem Fall Verweis auf 0x0011 mit 08 00 (.dw 0x0008)
; 0x0012 mit 02 00 (.dw 0x0200)
; 0x0013 mit 00 00 (.dw 0x0000)
; 0x0014 mit 04 00 (.dw 0x0400)
; 0x0015 mit 08 50 (.dw 0x0580)
; 0x0016 mit 02 87 (.dw 0x8702)
; 0x0017 mit 03 07 (.dw 0x0703)
; 0x0018 mit 00 00 (.dw 0x0000)
; 0x0019 mit 05 07 (.dw 0x0705)
; ->>>
; Der Wert(Tabellenlaenge) ist auch nur ein festgelegter Wert und fuer Programmoperationen wichtig.
;
RESET:
;
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
;
ldi temp,(1<<CS10)|(1<<WGM12) ; Taktfrequenz Vorteiler 1 gewaehlt (fuer Simaulatortest)
; ldi temp,(1<<CS10)|(1<<CS12)|(1<<WGM12) ; Taktfrequenz mit Vorteiler 1024 gewaehlt
out TCCR1B,temp
;
ldi temp,HIGH(Timerwert)
out OCR1AH,temp
ldi temp,LOW(Timerwert)
out OCR1AL,temp
;
ldi temp,(1<<COM1A1)|(0<<COM1A0)
out TCCR1A,temp
ldi temp,(1<<OCIE1A) ; Timer1 Overflow aktivieren
out TIMSK,temp
;
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out TAST_DDR, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out TAST_PIN, temp ;STK500 schaltet gegen GND - Taste gedreuckt (Pin==0)
out TAST_PORT, temp ;PullUp an PortD einschalten
out LED_DDR,Temp ;PORTB als Ausgang
out LED_PORT, temp ;PORTB (LEDs) aus
; Tastenzustände aller Tasten auf Null setzen
clr r16 ;
mov rTASTE0_ZST,r16 ; Taste0 und Taste1 zurücksetzen
mov rTASTE2_ZST,r16 ; Taste2 und Taste3 zurücksetzen
clr Zst_Var
;
sei
;
;
MAIN:
sbrc LED_Update_SPRR,0 ; kein LED_Update, wenn Bit0 ==0
rcall LED_Update
rjmp MAIN
;
LED_Update:
cbr LED_Update_SPRR,0 ; loesche Bit0, um LED_Update bis zur naechsten TIMER1_ISR zu sperren
ret
;
;/*-------------------------------------
;
;INTERRUPTDIENST TIMER1_COMPA_ISR
;
;Die Interruptdienstprogramm TIMER1_COMPA_ISR wird vom Output-Match-Interrupt von
;Timer1 ausgelöst. Es liest die aktuellen Messwerte der Tasten vom TAST_PIN ab und
;berechnet für jede Taste den neuen Zustand (Z-Wert).
;
;Eingansgrössen:
; keine
;
;Ausgangsgrössen:
; keine
;
;geänderte Register
; keine
;
;Anzahl Zyklen
;
;*/
TIMER1_COMPA_ISR:
push r25
in r25,SREG
push r16
push r17
push r18
push zl
push zh
;/*
;Das hier hattest Du übersehen:
;Der Zeiger auf die Übergangstabelle muss VOR (!!) dem Aufruf von NEXT0_TAST_ZST
;in die Register zh:zl geladen werden !!
;*/
ldi zl,LOW(TBL_UEBERGANG_01) ; zh:zl := Zeiger auf Übergangstabelle ...
ldi zh,HIGH(TBL_UEBERGANG_01) ; ...wird für NEXT0_TAST_ZST gebraucht
; Betätigungszustand der Tasten einlesen und der Reihe nach verarbeiten
in r18,TAST_PIN ; r18 := Messwerte aller Pins
; Taste0 verarbeiten
bst r18,TASTE0_PIN ; überträgt den Messwert von TASTE0_PIN ins T-Flag von SREG
mov r16,rTASTE0_ZST ; r16 := Z-Wert Taste0
andi r16,0x07 ; "Flankenbit" löschen
rcall NEXT0_TAST_ZST ; Folgezustand für Taste0 in r16 berechnen
mov rTASTE0_ZST,r16 ; neuen Z-Wert in TASTE0_ZST speichern
; Taste1 verarbeiten
bst r18,TASTE1_PIN ; überträgt den Messwert von TASTE1_PIN ins T-Flag von SREG
mov r16,rTASTE1_ZST ; r16 := Z-Wert Taste1
andi r16,0x07 ; "Flankenbit" löschen
rcall NEXT0_TAST_ZST ; Folgezustand für Taste1 in r16 berechnen
mov rTASTE1_ZST,r16 ; neuen Z-Wert in TASTE1_ZST speichern
; usw...
sbr LED_Update_SPRR,0 ; Status fuer LED_Update 1 - LED_Update / 0 - kein LED_Update
pop zh
pop zl
pop r18
pop r17
pop r16
out SREG,r25
pop r25
reti
;
;/*-------------------------------------
;PROZEDUR NEXT0_TAST_ZST
;
;Die Prozedur NEXT0_TAST_ZST liest den Folgezustand der Tastenentprellung aus einer
;Übergangstabelle aus. Die Adresse der Tabelle wird in zh:zl übergeben. Der aktuelle
;Zustand (Z-Wert) wird in r16 übergeben. Der aktuelle Tastenzustand (betätigt/nicht
;betätigt) ist im T-Flag von SREG gespeichert. Das Ergebnis wird in r16 zurückgegeben.
;Die Tabelle, darf maximal 127 Einträge enthalten. Der erste Eintrag der Tabelle ist
;die Länge der Tabelle und dient der Bereichsüberprüfung.
;
;D.h. der Zeiger auf die Übergangstabelle muss zh:zl geladen werden BEVOR diese
;Prozedur aufgerufen wird!! In diesem Beispiel, bei dem nur eine einzige Übergangstabelle
;im Spiel ist, könnte man zh:zl auch innerhalb von NEXT0_TAST_ZST laden. Um die
;Prozedur auch in Programmen verwenden zu können, in denen mehrere Zustandsautomaten
;nebeneinanderherlaufen, ist es sinnvoll, den Zeiger vorher zu laden. Dann kann je
;nach Aufgabe eine andere Tabelle verwendet werden. Kommt in meinen Programmen öfters
;vor ;-). ;
;
;Eingansgrössen:
; r16 : enthält aktuellen Z-Wert der Taste
; zh:zl : enthält Zeiger auf die Übergangstabelle
; SREG (T-Flag) : enthält den aktuellen Messwert der Taste
; T = 0 : Taste nicht betätigt
; T = 1 : Taste betätigt
;
;Ausgangsgrössen:
; r16 : enthält den neuen Z-Wert für die Taste
; zh:zl unverändert
; SREG unverändert
;
;geänderte Register
; r16
;
;Anzahl Zyklen
;
;*/
NEXT0_TAST_ZST:
push r17
in r17,SREG
push r17
push r18
push zl
push zh
; Zeiger auf Anfang der Übergangstabelle berechnen
add zl,zl ; zh:zl verdoppeln, weil einer Adresse im...
adc zh,zh ; .. Programmspeicher immer zwei Bytes entsprechen
; FRAGE: Wo wird die Information abgelegt -> im Flashspeicher
;
;
; Information in Bit7 löschen; Tabelle darf nicht länger als 127 Einträge sein
andi r16,0x7F ;
; Tabellenlänge einlesen (0-ter Eintrag in der Tabelle)
lpm r17,z+ ; r17 := TBL_LNG einlesen
; 1.Durchgang
; es wird in R16 der Wert 0x19 eingetragen - ??? Die letzte Stelle in der Tabelle (0x0019)
; und in R30(ZL) die 0x01
;
; 2.Durchgang
; es wird in R16 der Wert 0x19 eingetragen - ??? Die letzte Stelle in der Tabelle (0x0019)
; und in R30(ZL) die 0x01
;
; KLAR; Du hattest den Zeiger nicht gesetzt!
;
;
lpm r18,z+ ; zweites Byte überlesen, wird nicht gebraucht
; Bereichsprüfung ( r17 < TBL_LNG )
cp r16,r17 ; Tabellenindex mit TBL_LNG vergleichen
brlt NXT0ZST_00 ; Sprung, wenn Tabellenindex iO
; Fehler: Tabellenindex ist zu gross
clr r16 ; Zustand auf Null setzen
rjmp NXT0ZST_EXIT ; fertig
NXT0ZST_00:
; Tabellenindex im zulässigen Bereich
clr r17 ;
add zl,r16 ; Index zweimal zum Zeiger addieren, weil...
adc zh,r17 ; ... in der Tabelle...
add zl,r16 ; ... 2-Byte-Worte gespeichert sind.
adc zh,r17 ; zh:zl := Zeiger auf Tabelleneintrag
lpm r16,z+ ; r16 := Folgezustand für T=0
lpm r17,z ; r17 := Folgezustand für T=1
brtc NXT0ZST_EXIT ; fertig, wenn T = 0
; T = 1, d.h. Taste betätigt
mov r16,r17 ; zweiten Eintrag als Folgezustand verwenden
NXT0ZST_EXIT:
; sbr LED_Update_SPRR,0 ; Status fuer LED_Update 1 - LED_Update / 0 - kein LED_Update
; diese Zeile gehört in den Interruptdienst!
pop zh
pop zl
pop r18
pop r17
out SREG,r17
pop r17
ret
;
mare_crisium
19.02.2010, 18:43
robo_wolf,
das liegt an diesen Zeilen
ldi temp,(1<<COM1A1)|(0<<COM1A0)
out TCCR1A,temp
Laut Tabelle 50 auf Seite 119 des Datenblatts sagst Du Deinem 8515 damit, dass er beim Erreichen des Vergleichswortes den Pin OC1A auf "low" setzen soll ("Clear OC1A/OC1B on Compare Match (Set output to low level)").
Der Pin OC1A ist identisch mit Pin PD5. Du kannst das Umschalten aber nur erkennen, wenn der Pin vorher auf "high" gesetzt war. Ich nehme an, das ist der Grund, weshalb Du die Funktion erst beim dritten Interrupt sehen kannst. - Diese Doppelbelegungen der Pins können manchmal ziemlich tückisch sein ;-)!
Du kannst die Funktion abschalten, indem Du die Bits COM1A1 und COM1A0 beide auf Null stellst.
Ciao,
mare_crisium
robo_wolf
19.02.2010, 18:55
Hallo mare_crisium,
super...
Das wars. Mit den Timerfunktionen muss ich mich genauer auseinander setzen.
robo_wolf
22.02.2010, 20:24
Hallo mare_crisium,
hatte heute mal wieder genuegend Zeit um mich den schoenen Dingen im Leben zu widmen... ;-)
Hier nun das Ergebnis:
;***** STK500 Lernprogramm Nr.4e
;*** Aufgabe: die Taste an PinD0 schaltet die zugeordnete LED auf dem STK500
;*** 1. Tastendruck: LEDs einschalten
;*** 2. Tastendruck: LEDs ausschalten
;*** eine Timerroutine liest den TastenStatus ein
;*** zum Entprellen - muss die TimerRoutine 3mal hintereinander den gleichen Wert,
;*** einlesen um einen Tastzustand zu erkennen
;*** die Flanken der Tastzustaende werden in rTastFlanken gesammelt und schalten die LEDs um
;*** Taste nicht gedrueckt >>> log 0
;*** Taste gedrueckt >>> log 1
;
;
.include "m8515def.inc"
.def Temp = r16 ; Temporary Register
.def rTastFlanken = r20 ; Zustandsvariable - beinhaltet eine 2Bit-Zahl zur Tastzustandserkennung
.def rLED_Update_SPRR = r21 ; Sperrt die Ausfuehrung von TastenTest wenn, keine neuer TIMER1_OVL voran gegangen
;
.equ Timerwert = 155 ;-24999 ; Timerschritte bis zum Überlauf 0,2 sec
;
.equ LED_PORT = PORTB ; LEDs
.equ LED_DDR = PORTB-1 ; DataDirectory fuer LEDs
;
.equ TAST_PORT= PORTD ; Tasten
.equ TAST_DDR = PORTD-1 ; DataDirectory fuer TastenEingang
.equ TAST_PIN = PORTD-2 ; TastenEingang
;
; Pinzuordnung für div. Tasten
.equ TASTE0_PIN = 0
.equ TASTE1_PIN = 1
.equ TASTE2_PIN = 2 ; die Zuordnung der PINs muss ja nicht mit der TastenNr übereinstimmen!
.equ TASTE3_PIN = 3
.equ TASTE4_PIN = 4
.equ TASTE5_PIN = 5
.equ TASTE6_PIN = 6
.equ TASTE7_PIN = 7
;
; jetzt kommen die Register, die die Z-Werte der Tasten enthalten..
.def rTASTE1_0_ZST = r15 ; enthält Zustand Taste1 im oberen Nibble und 0 im unteren Nibble
.def rTASTE3_2_ZST = r14 ; enthält Zustand Taste3 im oberen Nibble und 2 im unteren Nibble
.def rTASTE5_4_ZST = r13 ; enthält Zustand Taste5 im oberen Nibble und 4 im unteren Nibble
.def rTASTE7_6_ZST = r12 ; enthält Zustand Taste7 im oberen Nibble und 6 im unteren Nibble
;
; Vektortabelle
;
rjmp RESET ; 1 - $000 RESET External Pin, Power-on Reset, Brown-out, Reset and Watchdog Reset
reti ; 2 - $001 INT0 External Interrupt Request 0
reti ; 3 - $002 INT1 External Interrupt Request 1
reti ; 4 - $003 TIMER1 CAPT Timer/Counter1 Capture Event
rjmp TIMER1_COMPA_ISR ; 5 - $004 TIMER1 COMPA Timer/Counter1 Compare Match A
reti ; 6 - $005 TIMER1 COMPB Timer/Counter1 Compare Match B
reti ; 7 - $006 TIMER1 OVF Timer/Counter1 Overflow
reti ; 8 - $007 TIMER0 OVF Timer/Counter0 Overflow
reti ; 9 - $008 SPI, STC Serial Transfer Complete
reti ; 10 - $009 USART, RXC USART, Rx Complete
reti ; 11 - $00A USART, UDRE USART Data Register Empty
reti ; 12 - $00B USART, TXC USART, Tx Complete
reti ; 13 - $00C ANA_COMP Analog Comparator
reti ; 14 - $00D INT2 External Interrupt Request 2
reti ; 15 - $00E TIMER0 COMP Timer/Counter0 Compare Match
reti ; 16 - $00F EE_RDY EEPROM Ready
reti ; 17 - $010 SPM_RDY Store Program memory Ready
;
TBL_UEBERGANG_01:
.dw 0x0008 ; Tabellenlänge
; T = 0 T = 1
.dw 0x0200 ; Z=0 -> Z=0,Flanke=0 / Z=2,Flanke=0
.dw 0x0308 ; Z=1 -> Z=0,Flanke=1 / Z=3,Flanke=0
.dw 0x0400 ; Z=2 -> Z=0,Flanke=0 / Z=4,Flanke=0
.dw 0x0501 ; Z=3 -> Z=1,Flanke=0 / Z=5,Flanke=0
.dw 0x0602 ; Z=4 -> Z=2,Flanke=0 / Z=6,Flanke=0
.dw 0x0703 ; Z=5 -> Z=3,Flanke=0 / Z=7,Flanke=0
.dw 0x0F04 ; Z=6 -> Z=4,Flanke=0 / Z=F,Flanke=1
.dw 0x0705 ; Z=7 -> Z=5,Flanke=0 / Z=7,Flanke=0
;
RESET:
;
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
;
ldi temp,(1<<CS10)|(1<<WGM12) ; Taktfrequenz Vorteiler 1 gewaehlt (fuer Simaulatortest)
; ldi temp,(1<<CS10)|(1<<CS11)|(1<<WGM12) ; Taktfrequenz mit Vorteiler 64 gewaehlt
out TCCR1B,temp
;
ldi temp,HIGH(Timerwert)
out OCR1AH,temp
ldi temp,LOW(Timerwert)
out OCR1AL,temp
;
ldi temp,(0<<COM1A1)|(0<<COM1A0)
out TCCR1A,temp
ldi temp,(1<<OCIE1A) ; Timer1 Overflow aktivieren
out TIMSK,temp
;
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out TAST_DDR, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out TAST_PIN, temp ;STK500 schaltet gegen GND - Taste gedreuckt (Pin==0)
out TAST_PORT, temp ;PullUp an PortD einschalten
out LED_DDR,Temp ;PORTB als Ausgang
out LED_PORT, temp ;PORTB (LEDs) aus
;
; Tastenzustände aller Tasten auf Null setzen
clr r16 ;
mov rTASTE1_0_ZST,r16 ; Taste0 und Taste1 zurücksetzen
mov rTASTE5_4_ZST,r16 ; Taste2 und Taste3 zurücksetzen
;
sei
;
MAIN:
sbrc rLED_Update_SPRR,0 ; kein LED_Update, wenn Bit0 ==0
rcall LED_Update
rjmp MAIN
;
LED_Update:
;
; LED_PORT auslesen - mit TastenZustand verarbeiten - LED_PORT ausgeben
push temp
in temp, LED_PORT ; LED_PORT einlesen
eor temp, rTastFlanken ; toggle das Bit im LED_PORT fuer das eine Flanke in rTastFlanken ansteht
out LED_PORT,temp ; LED_PORT augeben
cbr rLED_Update_SPRR,1 ; loesche Bit0, um LED_Update bis zur naechsten TIMER1_ISR zu sperren
clr rTastFlanken ; setze rTastFlanken zum erneuten Beschreiben auf 0x00
pop temp
ret
;
/*-------------------------------------
INTERRUPTDIENST TIMER1_COMPA_ISR
Die Interruptdienstprogramm TIMER1_COMPA_ISR wird vom Output-Match-Interrupt von
Timer1 ausgelöst. Es liest die aktuellen Messwerte der Tasten vom TAST_PIN ab und
berechnet für jede Taste den neuen Zustand (Z-Wert).
Eingansgrössen:
keine
Ausgangsgrössen:
keine
geänderte Register
keine
Anzahl Zyklen
*/
TIMER1_COMPA_ISR:
push r25
in r25,SREG
push r16
push r17
push r18
push zl
push zh
/*
Das hier hattest Du übersehen:
Der Zeiger auf die Übergangstabelle muss VOR (!!) dem Aufruf von NEXT0_TAST_ZST
in die Register zh:zl geladen werden !!
*/
ldi zl,LOW(TBL_UEBERGANG_01) ; zh:zl := Zeiger auf Übergangstabelle ...
ldi zh,HIGH(TBL_UEBERGANG_01) ; ...wird für NEXT0_TAST_ZST gebraucht
; Betätigungszustand der Tasten einlesen und der Reihe nach verarbeiten
in r18,TAST_PIN ; r18 := Messwerte aller Pins
com r18 ; invertier r18
;
; Taste0 und Taste1 verarbeiten und in Register TASTE1_0_ZST
;
; Taste0 verarbeiten (!!! Achtung unteres Nibble von rTASTE1_0_ZST 0bxxxx????)
clr r19 ; Register r19 fuer temporaeres Register fuer swap
bst r18,TASTE0_PIN ; überträgt den Messwert von TASTE0_PIN ins T-Flag von SREG
; lade TASTE1_0_ZST in R19 und loesche MSB (oberes Nibble)
mov r16,rTASTE1_0_ZST ; r16 := Z-Wert Taste0
sbrc r16,3 ; wenn Flankenbit gesetzt setze Bit1 in rTastFlanken
sbr rTastFlanken,0b00000001 ;
andi r16,0x07 ; "Flankenbit" und MSB löschen
rcall NEXT0_TAST_ZST ; Folgezustand für Taste0 in r16 berechnen
andi r16,0x0F ; loesche MSB fuer or
; schiebe rTASTE1_0_ZST in R19 und loesche LSB fuer neues Z von Taste0
mov r19, rTASTE1_0_ZST
andi r19,0xF0 ; loesche altes Z (LSB)
or r16, r19 ; vereine MSB und LSB in rTASTE1_0_ZST
mov rTASTE1_0_ZST,r16 ; neuen Z-Wert von Taste0 in TASTE1_0_ZST speichern
;
; Taste1 verarbeiten (!!! Achtung oberes Nibble von rTASTE1_0_ZST 0b????xxxx)
clr r19 ; Register r19 fuer temporaeres Register fuer swap
bst r18,TASTE1_PIN ; überträgt den Messwert von TASTE1_PIN ins T-Flag von SREG
; lade TASTE1_0_ZST in R19 und loesche MSB (oberes Nibble)
mov r19, rTASTE1_0_ZST
swap r19 ; tausche MSB mit LSB
sbrc r19,3 ; wenn Flankenbit gesetzt setze Bit2 in rTastFlanken
sbr rTastFlanken,0b00000010 ;
andi r19,0x07 ; loesche MSB (eigentlichliches LSB) und "Flankenbit"
mov r16,r19 ; r16 := Z-Wert Taste1
rcall NEXT0_TAST_ZST ; Folgezustand für Taste1 in r16 berechnen
swap r16 ; tausche MSB mit LSB
; schiebe rTASTE1_0_ZST in R19 und loesche MSB fuer neues Z von Taste1
mov r19, rTASTE1_0_ZST
andi r19,0x0F ; loesche altes Z (MSB)
or r16, r19 ; vereine MSB und LSB in rTASTE1_0_ZST
mov rTASTE1_0_ZST,r16 ; neuen Z-Wert von Taste1 in TASTE1_0_ZST speichern
;
; Taste2 und Taste3 verarbeiten und in Register TASTE3_2_ZST
;
; Taste2 verarbeiten (!!! Achtung unteres Nibble von rTASTE3_2_ZST 0bxxxx????)
clr r19 ; Register r19 fuer temporaeres Register fuer swap
bst r18,TASTE2_PIN ; überträgt den Messwert von TASTE2_PIN ins T-Flag von SREG
; lade TASTE3_2_ZST in R19 und loesche MSB (oberes Nibble)
mov r16,rTASTE3_2_ZST ; r16 := Z-Wert Taste0
sbrc r16,3 ; wenn Flankenbit gesetzt setze Bit3 in rTastFlanken
sbr rTastFlanken,0b00000100 ;
andi r16,0x07 ; "Flankenbit" und MSB löschen
rcall NEXT0_TAST_ZST ; Folgezustand für Taste2 in r16 berechnen
andi r16,0x0F ; loesche MSB fuer or
; schiebe rTASTE3_2_ZST in R19 und loesche LSB fuer neues Z von Taste2
mov r19, rTASTE3_2_ZST
andi r19,0xF0 ; loesche altes Z (LSB)
or r16, r19 ; vereine MSB und LSB in rTASTE3_2_ZST
mov rTASTE3_2_ZST,r16 ; neuen Z-Wert von Taste2 in TASTE3_2_ZST speichern
;
; Taste3 verarbeiten (!!! Achtung oberes Nibble von rTASTE3_2_ZST 0b????xxxx)
clr r19 ; Register r19 fuer temporaeres Register fuer swap
bst r18,TASTE3_PIN ; überträgt den Messwert von TASTE3_PIN ins T-Flag von SREG
; lade TASTE3_2_ZST in R19 und loesche MSB (oberes Nibble)
mov r19, rTASTE3_2_ZST
swap r19 ; tausche MSB mit LSB
sbrc r19,3 ; wenn Flankenbit gesetzt setze Bit4 in rTastFlanken
sbr rTastFlanken,0b00001000 ;
andi r19,0x07 ; loesche MSB (eigentlichliches LSB) und "Flankenbit"
mov r16,r19 ; r16 := Z-Wert Taste1
rcall NEXT0_TAST_ZST ; Folgezustand für Taste3 in r16 berechnen
swap r16 ; tausche MSB mit LSB
; schiebe rTASTE3_2_ZST in R19 und loesche MSB fuer neues Z von Taste1
mov r19, rTASTE3_2_ZST
andi r19,0x0F ; loesche altes Z (MSB)
or r16, r19 ; vereine MSB und LSB in rTASTE3_2_ZST
mov rTASTE3_2_ZST,r16 ; neuen Z-Wert von Taste3 in TASTE3_2_ZST speichern
;
; Taste5 und Taste4 verarbeiten und in Register TASTE5_4_ZST
;
; Taste4 verarbeiten (!!! Achtung unteres Nibble von rTASTE5_4_ZST 0bxxxx????)
clr r19 ; Register r19 fuer temporaeres Register fuer swap
bst r18,TASTE4_PIN ; überträgt den Messwert von TASTE4_PIN ins T-Flag von SREG
; lade TASTE5_4_ZST in R19 und loesche MSB (oberes Nibble)
mov r16,rTASTE5_4_ZST ; r16 := Z-Wert Taste0
sbrc r16,3 ; wenn Flankenbit gesetzt setze Bit5 in rTastFlanken
sbr rTastFlanken,0b00010000 ;
andi r16,0x07 ; "Flankenbit" und MSB löschen
rcall NEXT0_TAST_ZST ; Folgezustand für Taste4 in r16 berechnen
andi r16,0x0F ; loesche MSB fuer or
; schiebe rTASTE5_4_ZST in R19 und loesche LSB fuer neues Z von Taste4
mov r19, rTASTE5_4_ZST
andi r19,0xF0 ; loesche altes Z (LSB)
or r16, r19 ; vereine MSB und LSB in rTASTE1_0_ZST
mov rTASTE5_4_ZST,r16 ; neuen Z-Wert von Taste4 in TASTE5_4_ZST speichern
;
; Taste5 verarbeiten (!!! Achtung oberes Nibble von rTASTE5_4_ZST 0b????xxxx)
clr r19 ; Register r19 fuer temporaeres Register fuer swap
bst r18,TASTE5_PIN ; überträgt den Messwert von TASTE5_PIN ins T-Flag von SREG
; lade TASTE5_4_ZST in R19 und loesche MSB (oberes Nibble)
mov r19, rTASTE5_4_ZST
swap r19 ; tausche MSB mit LSB
sbrc r19,3 ; wenn Flankenbit gesetzt setze Bit6 in rTastFlanken
sbr rTastFlanken,0b00100000 ;
andi r19,0x07 ; loesche MSB (eigentlichliches LSB) und "Flankenbit"
mov r16,r19 ; r16 := Z-Wert Taste1
rcall NEXT0_TAST_ZST ; Folgezustand für Taste1 in r16 berechnen
swap r16 ; tausche MSB mit LSB
; schiebe rTASTE5_4_ZST in R19 und loesche MSB fuer neues Z von Taste5
mov r19, rTASTE5_4_ZST
andi r19,0x0F ; loesche altes Z (MSB)
or r16, r19 ; vereine MSB und LSB in rTASTE5_4_ZST
mov rTASTE5_4_ZST,r16 ; neuen Z-Wert von Taste5 in TASTE5_4_ZST speichern
;
; Taste6 und Taste7 verarbeiten und in Register TASTE7_6_ZST
;
; Taste6 verarbeiten (!!! Achtung unteres Nibble von rTASTE7_6_ZST 0bxxxx????)
clr r19 ; Register r19 fuer temporaeres Register fuer swap
bst r18,TASTE6_PIN ; überträgt den Messwert von TASTE6_PIN ins T-Flag von SREG
; lade TASTE7_6_ZST in R19 und loesche MSB (oberes Nibble)
mov r16,rTASTE7_6_ZST ; r16 := Z-Wert Taste0
sbrc r16,3 ; wenn Flankenbit gesetzt setze Bit7 in rTastFlanken
sbr rTastFlanken,0b01000000 ;
andi r16,0x07 ; "Flankenbit" und MSB löschen
rcall NEXT0_TAST_ZST ; Folgezustand für Taste6 in r16 berechnen
andi r16,0x0F ; loesche MSB fuer or
; schiebe rTASTE7_6_ZST in R19 und loesche LSB fuer neues Z von Taste6
mov r19, rTASTE7_6_ZST
andi r19,0xF0 ; loesche altes Z (LSB)
or r16, r19 ; vereine MSB und LSB in rTASTE7_6_ZST
mov rTASTE7_6_ZST,r16 ; neuen Z-Wert in TASTE6_ZST speichern
;
; Taste7 verarbeiten (!!! Achtung oberes Nibble von rTASTE7_6_ZST 0b????xxxx)
clr r19 ; Register r19 fuer temporaeres Register fuer swap
bst r18,TASTE7_PIN ; überträgt den Messwert von TASTE7_PIN ins T-Flag von SREG
; lade TASTE7_6_ZST in R19 und loesche MSB (oberes Nibble)
mov r19, rTASTE7_6_ZST
swap r19 ; tausche MSB mit LSB
sbrc r19,3 ; wenn Flankenbit gesetzt setze Bit8 in rTastFlanken
sbr rTastFlanken,0b10000000 ;
andi r19,0x07 ; loesche MSB (eigentlichliches LSB) und "Flankenbit"
mov r16,r19 ; r16 := Z-Wert Taste1
rcall NEXT0_TAST_ZST ; Folgezustand für Taste7 in r16 berechnen
swap r16 ; tausche MSB mit LSB
; schiebe rTASTE7_6_ZST in R19 und loesche MSB fuer neues Z von Taste7
mov r19, rTASTE7_6_ZST
andi r19,0x0F ; loesche altes Z (MSB)
or r16, r19 ; vereine MSB und LSB in rTASTE7_6_ZST
mov rTASTE7_6_ZST,r16 ; neuen Z-Wert von Taste7 rTASTE7_6_ZST speichern
;
;
;
sbr rLED_Update_SPRR,1 ; Status fuer LED_Update
pop zh
pop zl
pop r18
pop r17
pop r16
out SREG,r25
pop r25
reti
;
/*-------------------------------------
PROZEDUR NEXT0_TAST_ZST
Die Prozedur NEXT0_TAST_ZST liest den Folgezustand der Tastenentprellung aus einer
Übergangstabelle aus. Die Adresse der Tabelle wird in zh:zl übergeben. Der aktuelle
Zustand (Z-Wert) wird in r16 übergeben. Der aktuelle Tastenzustand (betätigt/nicht
betätigt) ist im T-Flag von SREG gespeichert. Das Ergebnis wird in r16 zurückgegeben.
Die Tabelle, darf maximal 127 Einträge enthalten. Der erste Eintrag der Tabelle ist
die Länge der Tabelle und dient der Bereichsüberprüfung.
D.h. der Zeiger auf die Übergangstabelle muss zh:zl geladen werden BEVOR diese
Prozedur aufgerufen wird!! In diesem Beispiel, bei dem nur eine einzige Übergangstabelle
im Spiel ist, könnte man zh:zl auch innerhalb von NEXT0_TAST_ZST laden. Um die
Prozedur auch in Programmen verwenden zu können, in denen mehrere Zustandsautomaten
nebeneinanderherlaufen, ist es sinnvoll, den Zeiger vorher zu laden. Dann kann je
nach Aufgabe eine andere Tabelle verwendet werden. Kommt in meinen Programmen öfters
vor ;-).
Eingansgrössen:
r16 : enthält aktuellen Z-Wert der Taste
zh:zl : enthält Zeiger auf die Übergangstabelle
SREG (T-Flag) : enthält den aktuellen Messwert der Taste
T = 0 : Taste nicht betätigt
T = 1 : Taste betätigt
Ausgangsgrössen:
r16 : enthält den neuen Z-Wert für die Taste
zh:zl unverändert
SREG unverändert
geänderte Register
r16
Anzahl Zyklen
*/
NEXT0_TAST_ZST:
push r17
in r17,SREG
push r17
push r18
push zl
push zh
; Zeiger auf Anfang der Übergangstabelle berechnen
add zl,zl ; zh:zl verdoppeln, weil einer Adresse im...
adc zh,zh ; .. Programmspeicher immer zwei Bytes entsprechen
;
;
; Information in Bit7 löschen; Tabelle darf nicht länger als 127 Einträge sein
andi r16,0x7F ;
; Tabellenlänge einlesen (0-ter Eintrag in der Tabelle)
lpm r17,z+ ; r17 := TBL_LNG einlesen
;
lpm r18,z+ ; zweites Byte überlesen, wird nicht gebraucht
; Bereichsprüfung ( r17 < TBL_LNG )
cp r16,r17 ; Tabellenindex mit TBL_LNG vergleichen
brlt NXT0ZST_00 ; Sprung, wenn Tabellenindex iO (wenn r16 kleiner r17)
; Fehler: Tabellenindex ist zu gross
clr r16 ; Zustand auf Null setzen
rjmp NXT0ZST_EXIT ; fertig
NXT0ZST_00:
; Tabellenindex im zulässigen Bereich
clr r17 ;
add zl,r16 ; Index zweimal zum Zeiger addieren, weil...
adc zh,r17 ; ... in der Tabelle...
add zl,r16 ; ... 2-Byte-Worte gespeichert sind.
adc zh,r17 ; zh:zl := Zeiger auf Tabelleneintrag
lpm r16,z+ ; r16 := Folgezustand für T=0
lpm r17,z ; r17 := Folgezustand für T=1
brtc NXT0ZST_EXIT ; fertig, wenn T = 0
; T = 1, d.h. Taste betätigt
mov r16,r17 ; zweiten Eintrag als Folgezustand verwenden
NXT0ZST_EXIT:
pop zh
pop zl
pop r18
pop r17
out SREG,r17
pop r17
ret
;
Im Simulator schaut es eigentlich so aus, ich es erwartet habe.
Aber als ich das Programm zum finalen Test in den µC uebertragen hatte, ging erst einaml gar nichts.. :-(
Nach ein paar Veraenderungen am Timer, stellte sich dann aber doch ein wenig Freude ein.
Leider konnte ich meinen Fehler bis jetzt noch nicht lokalisieren.
Mit meinen 8MHz Quarz wollte ich alle 0,2 sec eine ISR ausloesen.
Entweder meine Timerberechnung sind falsch, oder meine Timereinstellungen, oder mein STK500 betreibt den µC doch nicht mit dem Quarz..ODER?
Den Vorteiler habe ich mit 64 fuer den Timer1 gewaehlt. und vorladen wollte ich ihn mit -24999 dmit ich ca. auf 0,2 sec komme.
die Einstellungen habe ic hso gewaehlt:
ldi temp,(1<<CS10)|(1<<CS11)|(1<<WGM12) ; Taktfrequenz mit Vorteiler 64 gewaehlt
out TCCR1B,temp
;
ldi temp,HIGH(Timerwert)
out OCR1AH,temp
ldi temp,LOW(Timerwert)
out OCR1AL,temp
Desweiteren wollte ich noch eine "Gedaechtnisfunktion" mit einbauen - den letzten LED-Stand ins EEPROM uebertragen. So soll der letzte Status nach dem
Aus- und wieder Einschalten erhalten und geschalten werden.
????
Wie spreche ich das EEPROM genau an?
mare_crisium
24.02.2010, 18:02
robo_wolf,
das sieht doch schon gut aus :-) ! Meine Kommentare habe ich in den Quelltext geschrieben. Deine Fragen zum Timer habe ich auch dort beantwortet (siehe Anhang).
Das EEPROM ist ein sehr praktischer Bestandteil der ATmegas. Ich benutze zum Lesen und Schreiben ziemlich genau die Prozeduren die im Datenblatt auf Seite 22 und 23 vorgestellt werden. Die funktionieren sehr zuverlässig.
Ciao,
mare_crisium
robo_wolf
25.02.2010, 08:23
Hallo mare_crisium,
erst einmal wieder ein grosses DANKE.
Den Fehler mit den nichtbeachteten Comparemodus habe ich gestern morgen auch endeckt.
Der im Code stehende Wert von "155" kam durch das Probieren bzw. Fehlersuchen.
Der eigentliche Wert von "-24999" ist aber, wie Du schon angemerkt hast, auch falsch und haette "24999" fuer den Comparemodus heissen muessen.
Bei der Fehlersuche habe ich erst die Hardware ausgeschlossen, in dem ich ein reines Timerprogramm geschrieben hatte.
Dabei ist mir dann der Fehler aufgefallen.
Mit den veralteten Kommentaren hast Du vollkommen Recht. >>>Kopieren, >>>verbessern und dann doch >>>anders realisiert - dabei die Kommentare vergessen.
Hierzu braeuchte ich noch ein wenig Hilfe:
#####
"Gute Idee, die Flankenbits gleich mundgerecht fürs Togglen auszulagern :-)! Du kannst das
noch eleganter hinkriegen, indem Du die Tasten in umgekehrter Reihenfolge auswertest und
das Flankenbit immer von rechts in rTastFlanken einschiebst (mit „lsl“-Anweisung). Ich würde
den ganzen Vorgang direkt nach dem Aufruf von „NEXT0_TAST_ZST“ verschieben, dann erwischst
Du die Flanke sofort nachdem sie erkannt wurde."
#####
meinst Du es so?:
clc
sbrc r16,3
sec
lsl rTastFlanken
clc - loesche das CaryFlag "0"
sbrc r16,3 - ist das Bit3 von r16(FlankenBit) nicht gesetzt Ueberspringe das >>>
sec - setzen des CaryFlag "1"
lsl rTastFlanken - schiebt dann, in Abhaengigkeit vom FlankenBit, das gesetzte bzw. nicht gesetzte CaryFlag ins Register rTastFlanken.
xxxx xxxx ; xxxx xxx0 ; xxxx xx01 ; xxxx x011 ; xxxx 0111 ; xxx0 1111 ; xx01 1110 ; x011 1100 ; 0111 1001
// Frage: Warum nicht "lsr"? - Dann kann die bisherige Reihenfolge bestehen bleiben.
xxxx xxxx ; 0xxx xxxx ; 10xx xxxx ; 110x xxxx ; 1110 xxxx ; 1111 0xxx ; 0111 10xx ; 0011 110x ; 1001 1110
Das mit den macro ist ne tolle Sache.
Hatte ich auch noch nie verwendet. Und hier nun ein sinnvolles, praktisches Bsp.
>>> wiederkehrende Prozesse sollte man ausgliedern <<< :-)
Habe noch einen weiteren Fehler gefunden. Auch der hat sich mir erst in der Praxis geboten.
Nachdem ich ja das Problem mit dem Timer nicht gleich gefunden hatte, probierte ich unterschiedliche Einstellungen aus ( der Wert "155") z.B.
Meine Programm hat nur sehr schlecht, das gemacht was es sollte.
Das Druecken der Taste bewirkte zwar ein Einschalten und Ausschalten der LED ... aber nur wenn der ISR Abstand gross gewaehlt war
und auch nicht immer...
Mein Fehler ist, dass eine Flanke nur das FlankenBit auswerte. Dabei ausser Betracht lasse, wo diese herkommt.
Bit 3 von "F" oder von "8"... boeser Denkfehler
Eine Moeglichkeit ware nun zwei Register r0TastFlanken und r1TastFlanken mit der Auswertung von "F" und "8" anstatt von Bit 3 zu befuellen.
Ueber eine weitere Moeglichkeit denke ich noch nach.... :-)
mare_crisium
25.02.2010, 18:32
robo_wolf,
meinst Du es so?:
Jup, genau so - bis auf den Fehler, den diesmal ich eingebaut habe :oops: : Mit der "lsl"-Anweisung wird immer eine Null von rechts in das Register eingeschoben. Wenn man das Carry-Flag von rechts einschieben will, muss man die "rol"-Anweisung nehmen! Sorry. Es muss also so heissen:
clc
sbrc r16,3
sec
rol rTastFlanken
Natürlich kannst Du die Flankenbits auch von links her einschieben ("ror"-Anweisung); nur ist das Flankenbyte dann nicht automatisch rechtsbündig, d.h. bis Bit0 besetzt. Es sei denn, Du hättest genau 8 Tasten verarbeitet.
Bit 3 von "F" oder von "8"... boeser Denkfehler
Ich wollte Dir sowieso vorschlagen, einfach das Bit0 des Z-Wertes zum Steuern der LED zu verwenden. Das zeigt direkt den aktuellen Tastenzustand an. Das Flankenbit könntest Du verwenden, um zu entscheiden, ob die LED ihren Leuchtzustand ändern muss oder nicht. Sich nur auf die Flanke zu verlassen ist riskant. Wie Du ja festgestellt hast, kommt man da leicht aus dem Tritt.
Ciao,
mare_crisium
robo_wolf
28.02.2010, 20:56
Hallo mare_crisium,
habe den Code umgeschrieben und einige Probleme damit gehabt.
Thema macro:
eigendlich hatte ich mir ein macro fuer das Sichern der Flanke nach Deinen Tipps gemacht...
Aber bin dabei auf folgende Probleme gestossen:
Zum Einen hatte ich das macro unter die ISR gesetzt und mich dann beim assemblieren gewundert, warum 2 Fehlermeldungen kamen.
"unbekannte instruction oder macro.."
->>> macro muss vor dem Aufruf bekannt sein - habe es dann unter RESET abgelegt. -> Fehlermeldungen waren dann weg.
Mein 2. Problem tauchte dann auf als das macro ins Unterprogramm verzweigen sollte -
das Unterprogramm kam mit ret nicht mehr ins macro zurueck - dafuer aber leider zum RESET.
.macro _FLANKE_SICHERN
clc
cpi r16, 0x0F
brne SET_CARY_FLAG
rol rTastFlanken
.endmacro ;
;
SET_CARY_FLAG:
sec
ret
So habe ich dann doch sub´s verwendet.
FRAGE??? Das macro muss also vor dem Aufruf bekannt sein?
FRAGE??? Man kann im macro nicht verzweigen?
Habe mal wieder ein Gang zurueck geschaltet und nur Taste0 und Taste1 behandelt.
Ausserdem die ISR-Zeit deutlich eingekuerzt von 0,25 auf 0,05sec
0,25 war sehr traege - da ja erst nach dem 4. Durchlauf die Flanken gestetzt werden, sind es in Summe ja 1sec.
Das Auswerten der LOW_Flanke habe ich auch heraus gelassen - es soll ja nur bei HIGH eine Aenderung am LED_PORT geben.
;***** STK500 Lernprogramm Nr.4e1
;*** Aufgabe: die Taste an PinD0 schaltet die zugeordnete LED auf dem STK500
;*** 1. Tastendruck: LEDs einschalten
;*** 2. Tastendruck: LEDs ausschalten
;*** eine Timerroutine liest den TastenStatus ein
;*** zum Entprellen - muss die TimerRoutine 3mal hintereinander den gleichen Wert,
;*** einlesen um einen Tastzustand zu erkennen
;*** die Flanken der Tastzustaende werden in rTastFlanken gesammelt und schalten die LEDs um
;*** Taste nicht gedrueckt >>> log 0
;*** Taste gedrueckt >>> log 1
;
;
.include "m8515def.inc"
.def Temp = r16 ; Temporary Register
.def rTastFlanken = r0 ; Zustandsvariable - beinhaltet eine 2Bit-Zahl zur Tastzustandserkennung
.def rLED_Update_SPRR = r21 ; Sperrt die Ausfuehrung von TastenTest wenn, keine neuer TIMER1_OVL voran gegangen
;
.equ Timerwert = 6249 ; Timerschritte bis zum Überlauf 0,05 sec
;
.equ LED_PORT = PORTB ; LEDs
.equ LED_DDR = PORTB-1 ; DataDirectory fuer LEDs
;
.equ TAST_PORT= PORTD ; Tasten
.equ TAST_DDR = PORTD-1 ; DataDirectory fuer TastenEingang
.equ TAST_PIN = PORTD-2 ; TastenEingang
;
; Pinzuordnung für div. Tasten
.equ TASTE0_PIN = 0
.equ TASTE1_PIN = 1
.equ TASTE2_PIN = 2 ; die Zuordnung der PINs muss ja nicht mit der TastenNr übereinstimmen!
.equ TASTE3_PIN = 3
.equ TASTE4_PIN = 4
.equ TASTE5_PIN = 5
.equ TASTE6_PIN = 6
.equ TASTE7_PIN = 7
;
; jetzt kommen die Register, die die Z-Werte der Tasten enthalten..
.def rTASTE1_0_ZST = r15 ; enthält Zustand Taste1 im oberen Nibble und 0 im unteren Nibble
.def rTASTE3_2_ZST = r14 ; enthält Zustand Taste3 im oberen Nibble und 2 im unteren Nibble
.def rTASTE5_4_ZST = r13 ; enthält Zustand Taste5 im oberen Nibble und 4 im unteren Nibble
.def rTASTE7_6_ZST = r12 ; enthält Zustand Taste7 im oberen Nibble und 6 im unteren Nibble
;
; Vektortabelle
;
rjmp RESET ; 1 - $000 RESET External Pin, Power-on Reset, Brown-out, Reset and Watchdog Reset
reti ; 2 - $001 INT0 External Interrupt Request 0
reti ; 3 - $002 INT1 External Interrupt Request 1
reti ; 4 - $003 TIMER1 CAPT Timer/Counter1 Capture Event
rjmp TIMER1_COMPA_ISR ; 5 - $004 TIMER1 COMPA Timer/Counter1 Compare Match A
reti ; 6 - $005 TIMER1 COMPB Timer/Counter1 Compare Match B
reti ; 7 - $006 TIMER1 OVF Timer/Counter1 Overflow
reti ; 8 - $007 TIMER0 OVF Timer/Counter0 Overflow
reti ; 9 - $008 SPI, STC Serial Transfer Complete
reti ; 10 - $009 USART, RXC USART, Rx Complete
reti ; 11 - $00A USART, UDRE USART Data Register Empty
reti ; 12 - $00B USART, TXC USART, Tx Complete
reti ; 13 - $00C ANA_COMP Analog Comparator
reti ; 14 - $00D INT2 External Interrupt Request 2
reti ; 15 - $00E TIMER0 COMP Timer/Counter0 Compare Match
reti ; 16 - $00F EE_RDY EEPROM Ready
reti ; 17 - $010 SPM_RDY Store Program memory Ready
;
TBL_UEBERGANG_01:
.dw 0x0008 ; Tabellenlänge
; T = 0 T = 1
.dw 0x0200 ; Z=0 -> Z=0,Flanke=0 / Z=2,Flanke=0
.dw 0x0308 ; Z=1 -> Z=0,Flanke=1 / Z=3,Flanke=0
.dw 0x0400 ; Z=2 -> Z=0,Flanke=0 / Z=4,Flanke=0
.dw 0x0501 ; Z=3 -> Z=1,Flanke=0 / Z=5,Flanke=0
.dw 0x0602 ; Z=4 -> Z=2,Flanke=0 / Z=6,Flanke=0
.dw 0x0703 ; Z=5 -> Z=3,Flanke=0 / Z=7,Flanke=0
.dw 0x0F04 ; Z=6 -> Z=4,Flanke=0 / Z=F,Flanke=1
.dw 0x0705 ; Z=7 -> Z=5,Flanke=0 / Z=7,Flanke=0
;
RESET:
;
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
;
; ldi temp,(1<<CS10)|(1<<WGM12) ; Taktfrequenz Vorteiler 1 gewaehlt (fuer Simaulatortest)
ldi temp,(1<<CS10)|(1<<CS11)|(1<<WGM12) ; Taktfrequenz mit Vorteiler 64 gewaehlt
out TCCR1B,temp
;
ldi temp,HIGH(Timerwert)
out OCR1AH,temp
ldi temp,LOW(Timerwert)
out OCR1AL,temp
;
ldi temp,(0<<COM1A1)|(0<<COM1A0)
out TCCR1A,temp
ldi temp,(1<<OCIE1A) ; Timer1 Overflow aktivieren
out TIMSK,temp
;
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out TAST_DDR, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out TAST_PIN, temp ;STK500 schaltet gegen GND - Taste gedreuckt (Pin==0)
out TAST_PORT, temp ;PullUp an PortD einschalten
out LED_DDR,Temp ;PORTB als Ausgang
out LED_PORT, temp ;PORTB (LEDs) aus
;
; Tastenzustände aller Tasten auf Null setzen
clr r16 ;
mov rTASTE1_0_ZST,r16 ; Taste0 und Taste1 zurücksetzen
mov rTASTE5_4_ZST,r16 ; Taste2 und Taste3 zurücksetzen
;
sei
;????
; hier soll das Ruecksichern aus dem EEPROM erfolgen - ; lds temp, ????
; und ; out LED_PORT, temp
;!!!!
MAIN:
sbrc rLED_Update_SPRR,0 ; kein LED_Update, wenn Bit0 ==0
rcall LED_Update
rjmp MAIN
;
LED_Update:
;
; LED_PORT auslesen - mit TastenZustand verarbeiten - LED_PORT ausgeben
push temp
in temp, LED_PORT ; LED_PORT einlesen
eor temp, rTastFlanken ; toggle das Bit im LED_PORT fuer das eine Flanke in rTastFlanken ansteht
out LED_PORT,temp ; LED_PORT augeben
;????
; hier soll noch ins EEPROM gesichert werden ; sts ????, temp
; LED_PORT sichern
;!!!!
cbr rLED_Update_SPRR,1 ; loesche Bit0, um LED_Update bis zur naechsten TIMER1_ISR zu sperren
clr rTastFlanken ; setze rTastFlanken zum erneuten Beschreiben auf 0x00
pop temp
ret
;
/*-------------------------------------
INTERRUPTDIENST TIMER1_COMPA_ISR
Die Interruptdienstprogramm TIMER1_COMPA_ISR wird vom Output-Match-Interrupt von
Timer1 ausgelöst. Es liest die aktuellen Messwerte der Tasten vom TAST_PIN ab und
berechnet für jede Taste den neuen Zustand (Z-Wert).
Eingansgrössen:
keine
Ausgangsgrössen:
keine
geänderte Register
keine
Anzahl Zyklen
*/
TIMER1_COMPA_ISR:
push r25
in r25,SREG
push r1 ; Register r1 - temporaeres Register fuer Unterprogramme
push r20 ; Register r20 - temp. Bit fuer rTastFlanken(uebergibt Unterprogramm den Tastursprung)
push r16
push r17
push r18
push r19 ; Register r19 - temporaeres Register fuer swap
push zl
push zh
;
ldi zl,LOW(TBL_UEBERGANG_01) ; zh:zl := Zeiger auf Übergangstabelle ...
ldi zh,HIGH(TBL_UEBERGANG_01) ; ...wird für NEXT0_TAST_ZST gebraucht
;
; Betätigungszustand der Tasten einlesen und der Reihe nach verarbeiten
in r18,TAST_PIN ; r18 := Messwerte aller Pins
com r18 ; invertier r18
;
; Taste1 und Taste0 verarbeiten und in Register TASTE1_0_ZST
;
; Taste1 verarbeiten (!!! Achtung oberes Nibble von rTASTE1_0_ZST 0b????xxxx)
rcall CLEAR_REGISTER
ldi r20, 2 ; tmp. Tastenursprung - fuer Bitbestimmung in rTastFlanken
bst r18,TASTE1_PIN ; überträgt den Messwert von TASTE1_PIN ins T-Flag von SREG
; lade TASTE1_0_ZST in R1 und loesche MSB (oberes Nibble)
mov r1, rTASTE1_0_ZST ; temp Register fuer SET_XXX_NIBBLE - uebernimmt rTastx_y_ZST
rcall GET_HIGH_NIBBLE ; hole HIGH_NIBBLE nach r16
rcall FLANKE_SICHERN ;
andi r16,0x07 ; loesche MSB (eigentlichliches LSB) und "Flankenbit"
mov r19,r16 ; r16 := Z-Wert Taste1
rcall NEXT0_TAST_ZST ; Folgezustand für Taste1 in r16 berechnen
rcall SET_HIGH_NIBBLE ; bereite HIGH_NIBBLE fuer Ruecksichern vor
mov rTASTE1_0_ZST,r16 ; neuen Z-Wert von Taste1 in TASTE1_0_ZST speichern
;
; Taste0 verarbeiten (!!! Achtung unteres Nibble von rTASTE1_0_ZST 0bxxxx????)
rcall CLEAR_REGISTER
ldi r20, 1 ; tmp. Tastenursprung
bst r18,TASTE0_PIN ; überträgt den Messwert von TASTE0_PIN ins T-Flag von SREG
; lade TASTE1_0_ZST in R1 und loesche MSB (oberes Nibble)
mov r1, rTASTE1_0_ZST ; temp Register fuer SET_XXX_NIBBLE
rcall GET_LOW_NIBBLE ; hole LOW_NIBBLE nach r16
rcall FLANKE_SICHERN ;
andi r16,0x07 ; "Flankenbit" und MSB löschen
rcall NEXT0_TAST_ZST ; Folgezustand für Taste0 in r16 berechnen
rcall SET_LOW_NIBBLE ; bereite LOW_NIBBLE fuer Ruecksichern vor
mov rTASTE1_0_ZST,r16 ; neuen Z-Wert von Taste0 in TASTE1_0_ZST speichern
;
sbr rLED_Update_SPRR,1 ; setze Status fuer LED_Update
;
pop zh
pop zl
pop r20
pop r19
pop r18
pop r17
pop r16
pop r1
out SREG,r25
pop r25
reti
;
; Vergleich ob im Flanke fuer gedrueckte Taste(0xF) erreicht wurde -
; bei Erreichen wird eine 1 ; bei nicht Erreichen eine 0 in rTASTFLANKEN mit Hilfe des CARY_FLAGs von links nach rechts rotiert
;
CLEAR_REGISTER:
clr r1 ; Register r1 - temporaeres Register fuer Unterprogramme
clr r16 ; Register r16
clr r19 ; Register r19 - temporaeres Register fuer swap
clr r20 ; Register r20 - temporaeres Register fuer Unterprogramme
ret
;
GET_HIGH_NIBBLE:
mov r16, r1
swap r16 ; tausche MSB mit LSB
ret
;
GET_LOW_NIBBLE:
mov r16,r1 ; r16 := Z-Wert Taste0
ret
;
; Flanke sichern
; hatte es mit dem links schieben versucht, aber mit der zusaetzlichen Variablen r20 bin ich flexibler in der Tastenwahl
; FRAGE???
; meine Loesung funktioniert zwar - aber scheint umstaendlich
; wie kann ich es in einem Unterprogramm realisieren?
;
; setze Tast_FLAG wenn rTastx.. oder ..y_ZST 0b00001111 erreicht
FLANKE_SICHERN:
cpi r16, 0x0F
breq SET_TAST_FLAG
ret
;
SET_TAST_FLAG:
or rTastFlanken, r20
ret
;
SET_HIGH_NIBBLE:
swap r16 ; tausche MSB mit LSB
; schiebe rTASTE1_0_ZST in R19 und loesche MSB fuer neues Z von Taste1
mov r19, r1 ; hole rTASTx_y_ZST in r19
andi r19,0x0F ; loesche altes Z (MSB)
or r16, r19 ; vereine MSB und LSB in rTASTE1_0_ZST
ret
;
SET_LOW_NIBBLE:
; schiebe rTASTEx_y_ZST in R19 und loesche LSB fuer neues Z von jeweiligen Taste
andi r16,0x0F ; loesche MSB fuer or
mov r19, r1 ; hole rTASTx_y_ZST in r19
andi r19,0xF0 ; loesche altes Z (LSB)
or r16, r19 ; vereine MSB und LSB in rTASTE1_0_ZST
ret
;
/*-------------------------------------
PROZEDUR NEXT0_TAST_ZST
Die Prozedur NEXT0_TAST_ZST liest den Folgezustand der Tastenentprellung aus einer
Übergangstabelle aus. Die Adresse der Tabelle wird in zh:zl übergeben. Der aktuelle
Zustand (Z-Wert) wird in r16 übergeben. Der aktuelle Tastenzustand (betätigt/nicht
betätigt) ist im T-Flag von SREG gespeichert. Das Ergebnis wird in r16 zurückgegeben.
Die Tabelle, darf maximal 127 Einträge enthalten. Der erste Eintrag der Tabelle ist
die Länge der Tabelle und dient der Bereichsüberprüfung.
D.h. der Zeiger auf die Übergangstabelle muss zh:zl geladen werden BEVOR diese
Prozedur aufgerufen wird!! In diesem Beispiel, bei dem nur eine einzige Übergangstabelle
im Spiel ist, könnte man zh:zl auch innerhalb von NEXT0_TAST_ZST laden. Um die
Prozedur auch in Programmen verwenden zu können, in denen mehrere Zustandsautomaten
nebeneinanderherlaufen, ist es sinnvoll, den Zeiger vorher zu laden. Dann kann je
nach Aufgabe eine andere Tabelle verwendet werden. Kommt in meinen Programmen öfters
vor ;-).
Eingansgrössen:
r16 : enthält aktuellen Z-Wert der Taste
zh:zl : enthält Zeiger auf die Übergangstabelle
SREG (T-Flag) : enthält den aktuellen Messwert der Taste
T = 0 : Taste nicht betätigt
T = 1 : Taste betätigt
Ausgangsgrössen:
r16 : enthält den neuen Z-Wert für die Taste
zh:zl unverändert
SREG unverändert
geänderte Register
r16
Anzahl Zyklen
*/
NEXT0_TAST_ZST:
push r17
in r17,SREG
push r17
push r18
push zl
push zh
; Zeiger auf Anfang der Übergangstabelle berechnen
add zl,zl ; zh:zl verdoppeln, weil einer Adresse im...
adc zh,zh ; .. Programmspeicher immer zwei Bytes entsprechen
;
;
; Information in Bit7 löschen; Tabelle darf nicht länger als 127 Einträge sein
andi r16,0x7F ;
; Tabellenlänge einlesen (0-ter Eintrag in der Tabelle)
lpm r17,z+ ; r17 := TBL_LNG einlesen
;
lpm r18,z+ ; zweites Byte überlesen, wird nicht gebraucht
; Bereichsprüfung ( r17 < TBL_LNG )
cp r16,r17 ; Tabellenindex mit TBL_LNG vergleichen
brlt NXT0ZST_00 ; Sprung, wenn Tabellenindex iO (wenn r16 kleiner r17)
; Fehler: Tabellenindex ist zu gross
clr r16 ; Zustand auf Null setzen
rjmp NXT0ZST_EXIT ; fertig
NXT0ZST_00:
; Tabellenindex im zulässigen Bereich
clr r17 ;
add zl,r16 ; Index zweimal zum Zeiger addieren, weil...
adc zh,r17 ; ... in der Tabelle...
add zl,r16 ; ... 2-Byte-Worte gespeichert sind.
adc zh,r17 ; zh:zl := Zeiger auf Tabelleneintrag
lpm r16,z+ ; r16 := Folgezustand für T=0
lpm r17,z ; r17 := Folgezustand für T=1
brtc NXT0ZST_EXIT ; fertig, wenn T = 0
; T = 1, d.h. Taste betätigt
mov r16,r17 ; zweiten Eintrag als Folgezustand verwenden
NXT0ZST_EXIT:
pop zh
pop zl
pop r18
pop r17
out SREG,r17
pop r17
ret
;
mare_crisium
28.02.2010, 23:40
robo_wolf,
ja, beim Definieren und Benutzen der Macros gibt's Einiges zu beachten; das hätte ich ausführlicher erklären sollen. Alle Macros müssen vor der ersten Verwendung definiert sein. Die Definition darf sogar vor der CSEG- bzw. der ORG-Anweisung stehen. Ist eigentlich auch logisch, bei der Definition ist es ja noch nicht Teil des Quelltextes.
Noch wichtiger ist, mit den Sprüngen und "ret"-Anweisung innerhalb Macros sehr vorsichtig umzugehen. Ich rate davon ab, Sprunganweisgungen zu verwenden, die aus dem Macro herausführen. Da verliert man zu schnell den Überblick. Reine "ret"-Anweisung in einem Macro führen im Allgemeinen direkt ins Nirwana, weil meist der Stack nicht richtig wiederhergestellt wird, oder gar keine Rückkehradresse auf dem Stack liegt.
Vorschlag: Am Übersichtlichsten ist es, alle Macros ganz am Anfang des Quelltextes, noch vor der CSEG- oder ORG-Anweisung zu definieren. Dann weiss man immer, wo man nachgucken kann, wenn man mal wieder vergessen hat, was sie eigentlich bewirkten. Keine "ret"-Anweisungen in einem Macro ohne vorherige "call"- oder "rcall"-Anweisung in demselben Macro. Sprünge nur innerhalb des Macros zulassen.
Damit sähe Dein Macro dann so aus:
.macro _FLANKE_SICHERN
clc
cpi r16, 0x0F
brne PC+2
sec
rol rTastFlanken
.endmacro ;
Dabei verwende ich einen relativen Sprung: Die Anweisung "brne PC+1" bewirkt Folgendes: Wenn Gleichheit festgestellt wird, dann wird die Anweisung an der Adresse ausgeführt, auf die der PC (Program counter) gerade zeigt. Das ist die Anweisung direkt nach der "brne"-Anwesiung, also "sec". Wenn Ungleichheit festgestellt wird, dann springt das Programm zur Adresse PC+2, d.h. zur "rol"-Anweisung. Praktisch, gell :-) ? Das geht übrigens auch rückwärts, also PC-X. Die Sprungdistanz ist begrenzt (-128 bis +127).
Die "ret"-Anwweisung brauchst Du gar nicht. Das Macro verhält sich ja so, als ob Du ihren Quelltext mit "copy" und "paste" in den laufenden Quelltext eingefügt hättest.
Probier's mal, damit solltes klappen. Die Kommentare zu Nr. 4e1 kommen später :-) .
Ciao,
mare_crisium
EDIT: Den Fehler (Sprungweite) im Macro korrigiert.
mare_crisium
02.03.2010, 10:16
robo_wolf,
na, es doch geht voran ;-). Bevor wir neue "Features" in Angriff nehmen, ist es an der Zeit, sich noch einmal Überblick zu verschaffen, Überflüssiges bzw. Vermeidbares abzuschrauben und fertige Module in eine Bibliothek auszulagern (siehe Kommentare in angehängter pdf-Datei).
Als Quelltext darf ich's nicht einfügen, weil's angeblich mehr als 20000 Zeichen hat :-(. Leider musst Du den Text jetzt aus dem pdf herausfummeln.
Ciao,
mare_crisium
EDIT: Die Fehler verbessert (hoffentlich alle ;-) !), die robo_wolf entdeckt hat.
robo_wolf
02.03.2010, 19:47
Hallo mare_crisium,
vielen Dank fuer den neuen Lehrstoff. :-))
Habe gleich ein paar Fragen dazu.
Im MACRO _FLANKEN_SICHERN hast Du "@0" verwendet und als Parameter beschrieben.
.macro _FLANKEN_SICHERN
bst r16,3
bld rTAST_FLANKEN,(@0)
.endmacro
..muss das "@0" in Klammern()?
oder muss nur der zu uebergebene Parameter "LED_TASTE_x" beim macro-Aufruf
_FLANKEN_SICHERN(LED_TASTE_x) in Klammern?
Eins muss ich aber noch gleich loswerden:
"Es ist schon eine sehr galante und uebersichtliche Loesung."
Im Uebergang von Seite 4 auf 5 ist mir eine Ungereimtheit aufgefallen.
SET_LOW_NIBBLE:
push r25
in r25,SREG
andi r16,0x0F ; oberes Nibble des Wertes in r16 (Quelle) löschen
andi r19,0x0F ; unteres Nibble in r19 (Ziel) ebenfalls löschen
or r19, r16 ; neuens oberes Nibble in r19 einfügen
out SREG,r25
pop r25
ret
muss es hier nicht so heissen?(eventuell ein Tippfehler?)
SET_LOW_NIBBLE:
push r25
in r25,SREG
andi r16,0x0F ; oberes Nibble des Wertes in r16 (Quelle) löschen
andi r19,0xF0 ; unteres Nibble in r19 (Ziel) ebenfalls löschen
or r19, r16 ; neuens oberes Nibble in r19 einfügen
out SREG,r25
pop r25
ret
An meinen Kommentaren muss ich auf jeden Fall noch arbeiten.
Im Nachhinein liest sich vieles nicht unbedingt verstaendlich - meine Kommentare beschreiben oft nur den eigentlichen Programmschritt.
Das ist fuer mich momentan natuerlich relevant, da mir viele Befehle neu und nicht gelaeufig sind.
Der Sinn der Befehlsfolge bleibt dann oft auf der Strecke... :-( >>> Verbesserungspotential
mare_crisium
03.03.2010, 10:12
robo_wolf,
jup, hast recht mit Deinen Vermutungen: Die Prozedur SET_LOW_NIBBLE habe ich korrigiert, wie Du vorgeschlagen hast. Im Macro "FLANKEN_SICHERN" habe ich die Klammern gelöscht. Die korrigierte Version ist an meinem vorangehenden Posting angehängt.
Wo hast Du denn die "galante Lösung" her? Was ein Buchstabe doch für einen hübschen Unterschied machen kann :-) !
An den Kommentaren zu arbeiten lohnt sich immer. Der Aufwand für's Kommentieren wird aber mit der Zeit geringer, weil Du Deinen persönlichen Programmierstil entwickeln wirst. Das führt dann dazu, dass Du für typische Aufgaben bestimmte Befehlsfolgen verwenden wirst, deren Funktion Du auf Anhieb wiedererkennst. Dadurch erübrigen sich manche Kommentare von selbst.
Ciao,
mare_crisium
robo_wolf
03.03.2010, 22:30
Hallo mare_crisium,
in Deinem Posting vom 01.03. hast Du die Loesung mit dem Manipulieren des PC beim macro beschrieben.
Beim Testen im Simulator bin ich leider nicht zum gewuenschten Erfolg gekommen.
.macro _FLANKE_SICHERN
clc
cpi r16, 0x0F
brne PC+1
sec
rol rTastFlanken
.endmacro ;
PC+1 bewirkt doch nur einen Schritt weiter. In dem Fall des macro waere es "sec" bei Ungleichheit und bei Gleichheit ist es auch nur "sec". PC+2 waere dann "rol"-Anweisung und damit funktioniert es auch im Simulator.
Du wolltest mich testen? ;-)
mare_crisium
04.03.2010, 08:31
Du wolltest mich testen? ;-)
wenn ich's gewollt hätte, hättest Du mit 100 Punkten bestanden :-) !
Der Grund für meinen Fehler ist erstens, dass ich's nicht ausprobiert habe und zweitens meine Gewohnheit, PC immer so zu behandeln, als sei der aktuelle Befehl schon ausgeführt. Unter anderem deshalb benutze ich relative Sprünge nur noch in Macros. Mein Lieblingsfehler ist, zwischen Sprunganweisung und -ziel noch ein paar Anweisungen einzuflicken und nicht daran zu denken, die Sprungweite zu vergrösern. :oops: Diese Dinger sind nur mit allergrösster Vorsicht zu geniessen.
Ciao,
mare_crisium
robo_wolf
07.03.2010, 14:39
Hallo mare_crisium,
lang hats gedauert... (hatte leider nicht so viel Zeit - Sorry)
Aber nun moechte ich mein Werk wieder einmal vorstellen.
Vieles von Deinem Posting habe ich uebernommen, aber auch manches abgeaendert.
Aenderungen:
- Habe nun fuer High und Low-Flanken je ein Register rTAST_High_FLANKEN und rTAST_Low_FLANKEN(wird aber noch nicht ausgewertet)
- das marco _FLANKEN_SICHERN habe ich mit Abfragen auf HIGH und LOW erweitert
!!! wobei mir das noch nicht ganz gefaellt !!!
??? in der ISR wollte ich abfragen, ob eine Flanke anliegt und nur dann diese sichern
sbrc r16,3 ; Abfrage ob Flanke ansteht
_FLANKEN_SICHERN(LED_TASTE_0) ;
Das funktioniert nur leider nicht, musste ich feststellen.
Ist aber klar, wenn man sich anstelle des Aufrufs die Instruktionen vorstellt.
-> springe ich ja nur eine Instruktion weiter und nicht ueber das komplette macro
??? Kann ich bei rcall-Aufrufen auch ein Parameter mit uebergeben aehnlich wie beim _macro(@0) ???
- Register rLED_Update-Sprr ist ersetzt durch rIO_config
rIO_config soll mehr Infos aufnehmen als nur die Sperrung der Prozedur LED_Update
z.B. angedacht Lesen und Schreiben des EEPROM als Bit 1 und 2 usw...
- GET_HIGH_NIBBLE habe ich auch ein wenig veraendert
anstelle von
andi r16,0x0F ; unteres Nibble freistellen
kam:
andi r16,0x07 ; unteres Nibble freistellen und Flankenbit loeschen
die Zeile "andi r16,0x07" kam in der ISR im naechsten Schritt und wird somit ueberfluessig.
- den Kommentar habe ich auch angepasst und hoffe das es nun lesbarer ist - lese es am Besten in 3 Tagen noch einmal, um zu sehen ob es nun passt ;-)
// im grossen und ganzen muss ich sagen, wurde der Code durch das Auslagern der Unterprogramme wesentlich ueberschaubarer
so nun aber hier der Code:
diesmal angehaengt - wegen der 20.000er
mare_crisium
08.03.2010, 00:57
robo_wolf,
Ist aber klar, wenn man sich anstelle des Aufrufs die Instruktionen vorstellt.
-> springe ich ja nur eine Instruktion weiter und nicht ueber das komplette macro
esatto, so ist es. Wenn Du wüsstest, wie oft und wie lang ich schon nach Fehlern diesen Typs gesucht habe ;-) ! Das ist einer der Gründe, derentwegen ich möglichst nur Sprünge zu Labels verwende und keine relativen.
Ja, durch die Auslagerung ist Dein Quelltext jetzt schön übersichtlich und kompakt. Gefällt mir schon richtig gut :-) . Jetzt können wir noch versuchen, Programmplatz sparen, indem wir die Tastenabfrage im Interruptdienst auf eine Schleife umstellen.
Ja, prinzipiell kann man sich ein Macro schreiben, mit dem man die Parameterübergabe z.B. in die Form "_rcall(ProzedurName,Param1Byte,Param2Byte)" bringt; z.B.:
.macro _rcall
ldi r16,@1 ;
ldi zl,low(@2) ;
ldi zh,high(@2) ;
rcall @0 ;
.endmacro
Im laufenden Quelltext veranlasst die Anweisung
_rcall PROZEDURA, 0xFE, 0xABCD
_rcall PROZEDURB, 0xEF, 0xDCBA
dass Register r16 mit 0xFE, zl mit 0xCD und zh mit 0xAB geladen werden und anschliessend die Prozedur PROZEDURA aufgerufen wird. Anschliessend wird PROZEDURB mit 0xEF in r16 und 0xDCBA in zh:zl aufgerufen. Man muss nur aufpassen und die Veränderungen in r16, zl und zh nicht vergessen. Sehr gern passiert einem sowas
ldi r16,0x01
ldi zl,0x00
ldi zh 0x00
_rcall PROZEDURA, 0xFE, 0xABCD
or zl,zh
brne Nirwana
und man wundert sich, warum das Programm nach der "brne"-Anweisung nie nach "Nirwana" springt, obwohl es das ja tun müsste ;-) ... Das kommt davon, dass das Macro die Benutzung der Register r16, zl und zh versteckt. Umgehen liesse sich diese Gefahr, indem man die Sicherung der Register ebenfalls in das macro übernimmt, z.B. durch
.macro _rcall
push r16
push zl
push zh
ldi r16,@1 ;
ldi zl,low(@2) ;
ldi zh,high(@2) ;
rcall @0 ;
pop zh
pop zl
pop r16
.endmacro
Wenn man solche Vorsichtsmassnahmen trifft, kann man sich eine Macrosammlung aufbauen, mit der man Assemblerprogramme schreiben kann, die schon sehr einer Hochsprache ähneln.
Ausführlicherer Kommentar zu LernProgram_4g_V02 kommt später.
Ciao,
mare_crisium
robo_wolf
08.03.2010, 06:34
Hallo are_crisium,
mit der rcall-Aufrufen hatte ich nicht gemeint ein macro mit dem Namen rcall und Parameteruebergabe sondern ein Sprung zu einem Unterprogramm.
rcall FLANKEN_SICHERN
...
FLANKEN_SICHERN:
...
ret
Die Idee dahinter war, dass ich ebenfalls wie im macro _FLANKEN_SICHERN den Parameter(LED_TASTE_0) anhaengen wollte...
Trotzdem Danke fuer diese Erklaerung.
Wollte eh schon fragen:
Wenn man ein Paramter uebergibt, dann geht es auch mit mehr...?
mare_crisium
10.03.2010, 19:54
robo_wolf,
Hallo are_crisium,
Wenn man ein Paramter uebergibt, dann geht es auch mit mehr...?
ja, natürlich. Nur ist die Parameterübergabe immer implizit - damit meine ich: Der Benutzer muss vorher wissen, welcher Parameter in welchem Register steht. Solche praktischen Aufrufe, wie z.B. FLANKEN_SICHERN( LED_TASTE0), gibt's meines Wissens nur in Hochsprachen wie Pascal, aber in keinem Assemblerdialekt. - Deshalb muss man ein Macro gebasteln, um die Pascal-ähnliche Schreibweise zu simulieren.
Wenn man der Prozedur die Parameter in Registern übergeben muss, ist es hilfreich, eine Beschreibung der Prozedur zu haben, die angibt, welche Parameter in welchen Registern stehen müssen. Deshalb hat in meinen Programmen jede Prozedur so einen ausführlichen Vorspann. Andernfalls muss man erst den ganzen Quelltext der Prozedur wieder durchlesen und verstehen (;-)!) , bevor man sie benutzen kann.
Alternativ kann man der Prozedur auch Parameter übergeben, die im RAM stehen: Entweder auf dem Stack (so macht das Pascal) oder in einem Datenblock. Diese letzte Methode verwende ich auch in der angehängten Version Deines Lernprogramms. Da geht es nämlich um das Auslagern von Daten ins RAM :-) .
Dann man viel Spass!
Ciao,
mare_crisium
robo_wolf
11.03.2010, 17:37
Hallo mare_crisium,
wie sagte das Spitzohr bei StarTrek "fazinierend".... ;-)
Hast Dir wieder extrem viel Muehe gemacht. Danke
Auf den ersten Blick, gar nicht so leichte Kost fuer mich.
Da kommen garantiert noch ein paar Fragen auf Dich zu.
Da ist schon eine banale:
Was bedeutet die Abkuerzung TASTCLT? TAST ist klar..aber CLT
Banal, ich weis aber ich verstehe es sich leichter, wenn ich es weis.
LNG - laenge so was erklaert sich von selbst.
PS. bin leider 2 Tage auf Reisen und kann mich erst am Sonntag wieder melden.
mare_crisium
11.03.2010, 18:35
robo_wolf,
TASTCLT ist ein Dreckfuhler :-). Gemeint war TASTCTL (Tastensteuerung).
Ciao,
mare_crisium
P.S.: Wieso leider auf Reisen? Ach so: Dienstreise...
robo_wolf
15.03.2010, 14:16
so da is er wieder...
ah TAST en C on T ro L l
.db "...LernPrgm4g1_K01.asm..."
Den Gedanken finde ich gut.
aber wo finde ich den String im Simulator wieder? oder ist es nur nach dem Deassemblieren gemeint? Wenn man de nCode warten muss?
(TBL_UEBERGANG_01: - ist klar gehe mit dem Mauszeiger ueber den Eintrag und sehe die Speicherstelle
Nun kann ich mir gut vorstellen das der String davor steht, aber wenn er das tut, kann ich ihn nicht lesen...?)
Die RAM-Einteilung noch einmal zusammenfassend:
3 Bytes pro Tasten im RAM - Die Inistialisiereung mit "0" kann man im Simulator schoen erkennen.
??? Die Belegung ist so gemeint:
TASTCTLx Byte0 TASTCTLx Byte1 TASTCTLx Byte2
xxxx xxxx xxxx xxxx xxxx xxxx
TASTCTL_FLAGS TASTCTL_MASKE
----------------------
??? kann man den Code mit den Tasten0 - 7 nicht noch etwas schrumpfen in den man
; Taste0 einrichten
ldi yl,low(TASTCTL0) ; yh:yl := Zeiger auf ersten Datenblock
ldi yh,high(TASTCTL0)
ldi r17,(1<<TASTE0_PIN) ; Anschluss-Pin einstellen
std y+TASTCTL_MASKE,r17 ;
ihn eine weitere Schleife packt und darin alle Tasten durchlaeuft?
ldi TASTCTL_X, TASTCTL0 ; vorladen mit TASTCTL0 :=0
ldi TASTE_X_PIN, LED_TASTE_0 ; vorladen mit LED_TASTE_0 :=0
; TasteX einrichten
TasteX_Einrichten:
ldi yl,low(TASTCTL_X) ; yh:yl := Zeiger auf ersten Datenblock
ldi yh,high(TASTCTL_X)
ldi r17,(1<<TASTE_X_PIN) ; Anschluss-Pin einstellen
std y+TASTCTL_MASKE,r17 ;
ldi TASTCTL_X, TASTCTL_X + TASTCTL_LNG, ; Datenbereich um eine Tastenlaenge verschieben
ldi TASTE_X_PIN,TASTE_X_PIN + 1 ; TASTE_X_PIN um 1 erhoehen
cpi TASTE_X_PIN, TASTEN_ANZAHL ; TASTEN_ANZAHL erreicht dann fertig
breq TasteX_Einrichten ; Schleife solang durchlaufen, bis TASTEN_ANZAHL erreicht
ret
Das ist jetzt nur sinngemaess dahin geschrieben und kann man sicher noch viel eleganter loesen.(War eben so ein Gedanke)
Aber im nochmal zu Deinen geposteten LernPrgm4g1K01:
Bei den ersten Zeilen habe ich mich noch etwas schwer getan, aber nachdem ich alles durchgearbeitet hatte,
finde ich es doch sehr gelungen und sehr uebersichtlich.
mare_crisium
15.03.2010, 21:02
robo_wolf,
macht Spass, Dir beim Lernen zuzugucken :-) ! - Den Versionstext „......LernPrgm4g1_K01.asm... „ findest Du wieder, wenn Du Dir den Inhalt des Flashspeichers ansiehst. Das geht entweder im Simulator (über das Menu: „View“->“Memory“ im Memory-Fenster in der Combobox „Program“ anwählen. Den String erkennst Du in der rechten Fensterhälfte; dort wird der Speicherinhalt als ASCII angezeigt) oder, wenn das Programm in einen ATmega geflasht wurde, indem Du den Inhalt des Flashspeichers in den Disassembler hochlädst.
Dieser letzte Fall tritt auf, wenn Du mal anfängst, Dir eigene Schaltungen mit ATmegas drin zu bauen und zu programmieren. Mein erstes selbstgebautes Teil war ein RS232-TWI-Umsetzer (sehr nützliches Teil ;-) ). Die Schaltung hat einen Anschluss, über den ich das Program sowohl in den ATmega8 hinunter- als auch aus dem ATmega in den PC heraufladen kann. Natürlich kann man den Käfer auch sockeln, so dass man ihn herausziehen und zum Programmieren auf das STK500 umsetzen kann. Nur kann man dann keine SMD-Bauteile verwenden. Jedenfalls habe ich das Programm schon mehrfach hochgeladen und war froh, anhand des Versionstextes die Programmversion feststellen zu können. Früher hatte ich immer kleine Zettelchen mit der Versionsnummer auf die Chips geklebt. Aber heutzutage sind die SMD-Teile dafür zu winzig ... ;-) .
Die Speicherbelegung hast Du richtig verstanden. Eigentlich werden nur 2 Bytes pro Taste benutzt. Versuch' doch mal, was passiert, wenn folgende Änderungen machst
.equ TASTCTL_MASKE = 1 ; enthält die Nummer des Pins, an den die Taste
; angeschlossen ist
.equ TASTCTL_LNG = 2 ; Pro Taste 2 Bytes reserviert
(Nur um die Flexibilität dieser Art der Speicheraufteilung auszuprobieren ;-) ).
Die Schleife beim Einrichten der TASTCTL-Blöcke lässt sich machen; bringt aber einen Nachteil: Die Nummern der Tastenpins müssten in der Schleife berechnet werden, d.h. es muss einen festgelegten Zusammenhang zwischen Schleifenzähler und PinNummer geben (z.B. Tastennummer = Pin-Nummer). Das engt aber die Flexibilität bei der Zuordnung der Tasten ein.
Den Zeiger auf den Beginn des TASTCTL-Blocks der Taste mit der Nummer X kann man leider nicht so berechnen, wie Du es vorschlägst. Der ATMEL-Assembler erlaubt meines Wissens nicht, die Labels als Variablen zu benutzen. Er besteht darauf, dass Dein Label TASTCTL_X immer denselben Wert hat. Deine Anweisung
ldi TASTCTL_X, TASTCTL_X + TASTCTL_LNG, ; Datenbereich um eine Tastenlaenge verschieben
wird er strikt ablehnen.
Versuch' doch mal, einen Programmabschnitt in die Hauptschleife einzufügen, (vorzugsweise mithilfe einer Prozedur), die Dir zurückmeldet, ob eine Flanke ansteht (z.B. im Carry- oder im T-Flag) und welche Tastenzustände gerade anstehen (z.B. im Register r16). In diesem Zusammenhang könntest Du auch in TASTCTL ein Byte TASTCTL_LED einzuführen, das die Position des LED-Pins angibt, der die Taste darstellen soll. Dann kannst Du das Byte aus r16 direkt an die LEDs ausgeben, wenn eine Flanke ansteht. - Es ist besser, die Tastenzustände aus Bit0 des Z-Wertes abzuleiten, als bei jeder Flanke umzuschalten. - Du hast ja selbst schon gemerkt, wie man da aus dem Tritt kommen kann, wenn einmal eine Flanke unbemerkt durchwitscht.
Ciao,
mare_crisium
robo_wolf
17.03.2010, 22:16
Hallo mare_crisium,
bin leider noch nicht all soweit gekommen.
Zum Einen war ich auf Fehlersuche zum Anderen mach ich mir noch Gedanken, wie man die Tasten-Auswertung am Besten realisiert.
Der Fehler:
Bei gedrueckter Taste ist mir aufgefallen, dass ein Tabellenindexfehler erzeugt wurde.
Der Z-Wert sprang von 0x0F auf 0x00 anstatt auf 0x07(wo er dann ohne Pegelaenderung verharren sollte).
Bei losgelassener Taste natuerlich dann auch.
Ursache:
Das Flankenbit wurde in der vorhergehenden Programmversion in TAST_UTILS_V01.asm bei "GET_HIGH_NIBBLE:" geloescht.
..und das ist in der neuen Version nicht mehr eingebunden. Aus diesem Grund gibt es nun eine TAST_UTILS_V02.asm.
Dort wird es jetzt in "NEXT_TAST_ZST:" bei der Beschraenkung der Tabelleneintraegen von ehemals 127(0x7F) auf 119(0x77) geloescht.
Nun ja, da haette ich es auch gleich ablegen koennen.. ;-)
Gedanken zur Auswertung der Tasten und Flanken:
-----------------------------------------------
Daten im R A M
Bsp Taste0 bei gedrueckter Taste.:
-----------------------------------------------
TASTCTL0 Byte0 TASTCTL0 Byte1 TASTCTL0 Byte2
-----------------------------------------------
TASTCTL_FLAGS TASTCTL_MASKE
-----------------------------------------------
0000 0010 0000 0000 0000 0001
0x02 0x00 0x01
-----------------------------------------------
0000 0100 0000 0000 0000 0001
0x04 0x00 0x01
-----------------------------------------------
0000 0110 0000 0000 0000 0001
0x06 0x00 0x01
-----------------------------------------------
0000 1111 0000 0000 0000 0001
0x0F 0x00 0x01
-----------------------------------------------
0000 0111 0000 0000 0000 0001
0x07 0x00 0x01
-----------------------------------------------
|| || ||
----------------------------
Bsp Taste0 losgelassen(high bzw. gedrueckt voraus gegangen).:
-----------------------------------------------
0000 0111 0000 0000 0000 0001
0x07 0x00 0x01
-----------------------------------------------
0000 0101 0000 0000 0000 0001
0x05 0x00 0x01
-----------------------------------------------
0000 0011 0000 0000 0000 0001
0x03 0x00 0x01
-----------------------------------------------
0000 0001 0000 0000 0000 0001
0x01 0x00 0x01
-----------------------------------------------
0000 1000 0000 0000 0000 0001
0x08 0x00 0x01
-----------------------------------------------
0000 0000 0000 0000 0000 0001
0x00 0x00 0x01
-----------------------------------------------
Flankenbit - Bit3
Taste gedrueckt: Bit0 :=0 bis Flanke eintritt dann 1
Taste losgelassen: Bit0 :=1 bis Flanke eintritt dann 0
-> Taste gedrueckt Bit0 :=0 -> dann Flankenbit -> Bit0 :=1 == Taste als High(geschaltet) erkannt
-> Taste nicht gedrueckt Bit0 :=1 -> dann Flankenbit -> Bit0 :=0 == Taste als Low( nicht geschaltet) erkannt
===> Auswertung mit Flankenbit auf Bit0
liegt FlankenBit an und ist Bit0 :=0 dann Tasten nicht gedrueckt
liegt FlankenBit an und ist Bit0 :=1 dann Tasten gedrueckt
------------------------------------------------
im Programm "Tasten_4g_V02" wurden die Flanken mit einem macro in der ISR in ein Register gesichert.
neu soll:
in Main-Schleife - vorangegngene ISR setzt Bit(1) - in rIO_Config fuer Erlaubnis zum FlankenBit testen - Flanke (gesetzt) - Ausgang(LED)Update - Rueckehr in Main-Schleife
in Main-Schleife - vorangegngene ISR setzt Bit(1) - in rIO_Config fuer Erlaubnis zum FlankenBit testen - Flanke (n.gesetzt)- Rueckehr in Main-Schleife
in Main-Schleife - vorangegngene ISR setzt Bit(0) - Rueckehr in Main-Schleife
// bin noch an der richtigen Ausarbeitung und hoffe es bis zum WE noch verneunftig umzusetzen
// habe dann noch weitere Plaene mit der Schaltung :-) - mir schwebt eine Menuefunktion vor, mit der das LED-Update veraendert wird
PS. irgendwie verschwinden meine Leerzeichen im Posting :-( - deswegen das RAM im Code-Schnipsel.
mare_crisium
18.03.2010, 19:04
robo_wolf,
Du bewegst Dich schon sehr souverän in unserem Programm :-) .
Am Beispiel des Flankenbit-Fehlers kann man gut sehen, wie wichtig es war, von vornherein die Tabellen-Indexprüfung einzubauen. Nur dadurch konntest Du die Fehlerursache sofort erkennen. Hätten wir die Prüfung weggelassen, hätte das Programm plötzlich komische Z-Werte geliefert und Du hättest sehr viel mehr Zeit gebraucht, um den Fehler ausfindig zu machen. Also: Die Programmiermethode "diesen Fall brauche ich nicht zu berücksichtigen, er kann ja gar nicht vorkommen" führt zu labilen Programmen. Was passieren kann, wird früher oder später auch passieren (noch ein Korollar zu Murphy's law ;-) ).
Die andere Lösung für das Flankenbit-Problem ist eine modifizierte Übergangstabelle
TBL_UEBERGANG_01:
.dw 0x0010 ; Tabellenlänge
; T = 1 T = 0
.dw 0x0200 ; Z=0 -> Z=2,Flanke=0 / Z=0,Flanke=0 /
.dw 0x0308 ; Z=1 -> Z=3,Flanke=0 / Z=0,Flanke=1 /
.dw 0x0400 ; Z=2 -> Z=4,Flanke=0 / Z=0,Flanke=0 /
.dw 0x0501 ; Z=3 -> Z=5,Flanke=0 / Z=1,Flanke=0 /
.dw 0x0602 ; Z=4 -> Z=6,Flanke=0 / Z=2,Flanke=0 /
.dw 0x0703 ; Z=5 -> Z=7,Flanke=0 / Z=3,Flanke=0 /
.dw 0x0F04 ; Z=6 -> Z=F,Flanke=1 / Z=4,Flanke=0 /
.dw 0x0705 ; Z=7 -> Z=7,Flanke=0 / Z=5,Flanke=0 /
.dw 0x0200 ; Z=8 -> Z=2,Flanke=0 / Z=0,Flanke=0 /
.dw 0x0000 ; Z=9 -> Z=0,Flanke=0 / Z=0,Flanke=0 /
.dw 0x0000 ; Z=A -> Z=0,Flanke=0 / Z=0,Flanke=0 /
.dw 0x0000 ; Z=B -> Z=0,Flanke=0 / Z=0,Flanke=0 /
.dw 0x0000 ; Z=C -> Z=0,Flanke=0 / Z=0,Flanke=0 /
.dw 0x0000 ; Z=D -> Z=0,Flanke=0 / Z=0,Flanke=0 /
.dw 0x0000 ; Z=E -> Z=0,Flanke=0 / Z=0,Flanke=0 /
.dw 0x0705 ; Z=F -> Z=7,Flanke=0 / Z=5,Flanke=0 /
;
Du siehst, ich habe sie um 8 zusätzliche Zustände erweitert. Dadurch kann man jetzt festlegen, welcher Z-Wert bei gesetztem Flankenbit angenommen werden soll - man braucht es also nicht mehr per Programm zu löschen.
Das sieht zuerst ziemlich banal aus, es steckt aber mehr dahinter: Beim Programmieren gibt's immer den Dualismus zwischen Algorithmus und Datenstruktur; so ähnlich wie der Teilchen-Welle-Dualismus in der Quantenphysik. Der erste, der auf diesen Sachverhalt hingewiesen hat, war, meines Wissens, Prof. Niklaus Wirth von der ETH in Zürich. Er hat darüber ein bis heute sehr lesenswertes Buch verfasst (Niklaus Wirth, "Algorithmen und Datenstrukturen", B.G. Teubner 1983, ISBN 3-519-02250-8. ). Er zeigt, dass man beim Programmieren immer die Wahl hat, einen bestimmten Ablauf entweder in der Datenstruktur (z.B. in Tabellen) oder als Algorithmus (z.B. einer "case"-Abfrage) abzubilden. Eigentlich habe ich die Übergangstabelle in unserem Beispiel nur eingeführt, damit Du 'mal beide Seiten kennenlernst. Welchen der beiden Wege man wählt, hängt immer von der Anwendung und vom persönlichen Geschmack ab. Ich nehme öfters den Weg über die Datenstruktur, weil komplizierte Abläufe so oft übersichtlicher und wartungsfreundlicher darzustellen sind.
Im übrigen ist das Beispiel von Prof. Wirth auch ein lehrreiches Kapitel Technikgeschichte: Er entwickelte die Programmiersprachen Lillith, Pascal, Modula und Oberon. Unter seiner Leitung genoss die Informatikforschung der ETH Zürich einen weltweit führenden Ruf. - Aber es war die Zeit, als in Deutschland allen Ernstes eine Diskussion über die Einführung einer "Computersteuer" geführt wurde und die Mittel für die Informatikforschung (auch in Deutschland) stark eingeschränkt, wenn nicht gar zusammengestrichen wurden. - Na, schlussendlich war spätestens nach seiner Emeritierung die Informatikforschung in der Schweiz so gut wie nicht mehr existent. Genau wie in Deutschland zog man es vor, einmalige Gelegenheiten gemütlich zu verschlafen 8), mit den bekannten Folgen.
Bin gespannt auf Deine nächste Version!
Ciao,
mare_crisium
robo_wolf
21.03.2010, 16:52
Hallo mare_crisium,
nach dem der gestrige Tag im Zeichen des Gartens stand, melde ich mich erst heute... 8-[
Habe nun den Code wieder so angepasst, dass die gewuenschte Funktion "Ein- und Ausschalten" der LED mittels der jeweiligen Taste geschieht.
so 100%ig gefaellt es mir aber noch nicht...
eventuell liegt es an dem was mir so noch im Hinterkopf spukt:
>>>>
... eine Menueabfrage einfuegen.
Diese soll aufgerufen werden, wenn Tasten0 und Taste7 3 sec lang gedrueckt wurde.
--- denkbar ware hier das reservierte DatenByte (bzw. noch ein Zweites) im RAM zu nutzen - Timer ist bei 0,1 sec -> also 300 mal hoch zaehlen... ---
--- oder kann ich das TIMER1 COMPB eventuell dafuer nutzen? ---
--- sind die Werte beider Tasten > 300 dann Einstieg ins Menue ---
Im Menue stehen dann die einzelnen Tasten mit unterschiedlichen Funktionen zur Verfuegung. Signalisierung fuer Menue koennte ein Blinken der LEDs sein...
Nach Erkennen einer Taste wird das Menue verlassen und das Programm arbeitet mit der ausgewaehlten Funktion,
die hinter der Taste steht.
Wurde 5 sec lang, keine Eingabe gemacht, wird das Menue wieder verlassen.
--- denkbar auch hier ein Datenbyte auf 500 zaehlen ---
Als default soll das Programm mit SWITCH_LED nach dem Starten/Einschalten laufen bzw. zu einem spaeteren Zeitpunkt
soll das Programm nach dem Starten/Einschalten mit der zuletzt ausgewaehlten Funktion arbeiten.
<<<<
Das sind erst einmal nur Ueberlegungen... eventuell loese ich die Aufgabe dann doch etwas anders.
Auf Deine Kommentare und event. Vorschlaege bin ich wieder sehr gespannt.
PS. "Computersteuer" - so etwas Aehnliches hat man doch endlich geschafft :-k - in Form der GEZ-Gebuehren
robo_wolf
23.03.2010, 06:48
Hallo mare_crisium,
fuer mein Vorhaben mit dem Menue muss SWITCH_LED aus der TAST_ANA rausgenommen werden.
Dazu wiederum sollten die aus dem Z-Werten erkannten, betaetigten Tasten gesichert werden.
Entweder in ein Register oder im RAM.
Dann muss TAST_ANA noch eine Abfrage auf das Menue erhalten, damit der Code dorthin verzweigen kann.
ABER:
laut meinen Vorhaben gibt es nun 4 Timings
1 - fuer das Tastenentprellen 0,1sec
2 - fuer den MenueEinstieg 3,0sec
3 - fuer die MenueSignalisierung(Blinken) 0,2sec
4 - fuer den Menueausstieg 5sec
(wobei -2- =30 mal -1-, -3- =2 mal -1- ist und -4- direkt an -3- gebunden werden kann -4- =25 mal -3-)
- sicher hast Du da noch eine bessere Idee :-k
mare_crisium
23.03.2010, 07:50
robo_wolf,
klar, habe ich einen Vorschlag in der Mache :-) ! Ob er besser sein wird, weiss ich noch nicht. Dauert aber noch was...
Ciao,
mare_crisium
robo_wolf
23.03.2010, 08:17
Hallo mare_crisium,
"Ob er besser sein wird" - da bin ich mir ziemlich sicher ;-)
..naja, mein Vorschlag hat natuerlich den Nachteil, wenn ich den Timer1 veraendere, aendern sich alle anderen gleich mit.
mare_crisium
23.03.2010, 19:58
robo_wolf,
hier ist noch ein Zwischenschritt auf dem Weg zur Menusteuerung. In der .pdf-Datei habe ich versucht zu erklären, warum ich den für nötig halte. So gut ich's konnte, halt :-).
Morgen kommt der nächste Schritt mit einem Testprogramm für die Komponenten der Menusteuerung.
Ciao,
mare_crisium
robo_wolf
24.03.2010, 07:16
Hallo mare_crisium,
vielen Dank fuer die tolle Ausarbeitung.
Die IDEE eines gekapselten "IO-Treibers" kann man nun sehr schoen erkannen.
Habe mal einen sehr einfach gehaltenen Signalfluss aufgezeichnet.
robo_wolf
24.03.2010, 21:54
Hallo mare_crisium,
habe im Simulator 2 Problemchen bemerkt.
Beim Initialisieren der Ports
LED_PORT habe ich noch ser r16 eingefuegt, da sonst r16 noch den Wert von "ldi r16,(1<<OCIE1A) ; Timer1 Overflow aktivieren" hatte.
Beim TASTCTL_PORT habe ich noch "out TASTCTL_PINS, r16" eingefuegt, da die Tasten gegen GND geschaltet werden.(Besonderheit beim STK500).
-- sehr schoene Loesung --
...wenn ich das mal so sagen darf
Auf Deine Menueloesung bin ich schon sehr gespannt.
robo_wolf
29.03.2010, 22:11
Fuer den weiteren Aufbau des Programm und mit Hinsicht auf Kapselung habe ich nun Nachfolgendes:
Modul Menuesteuerung:
Das Menue wird aufgerufen durch das gleichzeitige Druecken der Tasten T0 und T7 fuer 3sec(Menue-Abfrage).
1 - nach dem Aufruf des "Menue-Auswahl" bleibt dies ohne weiteren Eingriff(Tastendruck)
5sec aktiv und beendet sich nach abgelaufender Zeit mit dem Ruecksprung zu MainSchleife.
2 - wird waehrend des Aufrufs von "Menue-Auswahl"ein Taste gedrueckt,
so schreibt der Inhalt von R18 in R15 geschoben
T0 := Bit0.... T7 :=Bit7 von rTastMenue und bendet "Menue-Steuerung" mit dem Ruecksprung zu Main-Schleife.
3 - Register r18 enthaelt die gedrueckten Tasten(High-Flanken)
-> r18 wird fuer die "Menuesteuerung" eingelesen und verarbeitet.
4 - fuer die Ausarbeitung und Erstellung des Testprogramms wird folgendes festgelegt:
Der Zeitgeber wird durch ein RegisterZaehler simuliert.
.equ MenueInTime = 3
.equ MenueTimeOut = 5
def r15 = rTastMenue ; wird default mit 0x01 geladen
def r20 = rMenueTC ; (MenueTimer-Counter)
F U N K T I O N:
Menue-Abfrage:
r18 wird in einer Schleife mit 0x81 (0b1000 0001) verglichen.
bei Uebereinstimmung incrementiert rMenueTC.
rMenueTC wird mit MenueInTime verglichen.
bei Uebereinstimmung wird das Untermenue "Menue-Auswahl" aufgerufen
und rMenueTC wird mit MenueTimeOut geladen.
Menue-Auswahl:
Menue-Auswahl durchlaeuft eine Schleife, in der rMenueTC decrementiert.
Bei "tst rMenueTC" wird rTastMenue mit 0x01 geldaen und die Funktion mit dem Ruecksprung in Main beendet.
oder die naechste erkannte Taste in R18 wird in R15 verschoben und das Menue-Auswahl damit beendet.
Innerhalb von Menue-Auswahl sollen die LEDs zur Signalisierung blinken.
Dazu wird rTastMenue geloescht := 0x00.
rTastMenue wird mit der naechsten in der Main-Schleife befindlichen Prozedur "SWITCH_OUT_PORT" bislang "SWITCH_LED" verarbeitet.
rTastMenue := 0x00 liefert das Blinken aller LEDs
mare_crisium
30.03.2010, 10:20
robo_wolf,
Du weisst ja, ich bin momentan zeitlich ziemlich eingeschränkt. Deshalb zeigen die Dateien im Anhang noch einen nicht komplett ausgetesten Zwischenstand. - Interessant, wie sich unsere Ansätze unterscheiden :-) . Das Ausknobeln hat richtig Spass gemacht und war eine gute Ablenkung.
Ciao,
mare_crisium
EDIT_1: Verbesserte Versionen der drei Anhänge hochgeladen
EDIT_2: Fehler (siehe Post von robo_wolf 02.04.2010) in _V03 korrigiert. Neue Version "TestPrgrm_04i_V03.asm"
EDIT_3: Neue Version _V09 mit Korrektur/Ergän
zung des Übergangskriteriums von A.0 nach A.1.
EDIT_4: Fehler (siehe Post von robo_wolf 10.04.2010) in "MENU8STRG_V02.asm" korrigiert, korrigierte Version hochgeladen
mare_crisium
30.03.2010, 20:03
robo_wolf,
bitte, lade die korrigierten Versionen herunter, die jetzt an meinem Posting von heute morgen angefügt sind. Bei den alten Versionen war ich doch zu sehr in Eile. Mit den neuen klappt's :-).
Ciao,
mare_crisium
robo_wolf
31.03.2010, 19:55
Hallo mare_crisium,
habe mir gestern abend noch die 1.Version geladen. Bin aber nicht mehr dazu gekommen alles zu testen.
Werde mir gleich einmal die neue Version laden und sie mir zu Gemuete fuehren. Vorab erst einmal wieder ein grosses Daaaankeschoen.
robo_wolf
02.04.2010, 08:34
Hallo mare_crisium,
habe mir nun mal alles im Simulator angeschaut.
Und bin gleich beim Start auf Probleme gestossen.
.device ATmega8515
.include "m8def.inc"
Was bewirkt ".device ATmega8515".
Durch die inc vom mega8 wurde sofort beim Sprung in eine Sub ein Fehlermeldung ausgegeben. ->nicht initialisierter Stack.
Mein Simulator simuliert den Mega8515 der hat nur halb so viel Speicher, wie der Mega8.
Habe deswegen die inc auf Mega8515 umgestellt.
Dann lief es auch normal durch.
melde mich erst wieder nach den Osterfeiertage - bin wieder mal auf Reisen
wuensche ein Frohes Osterfest
mare_crisium
02.04.2010, 11:38
robo_wolf,
die beiden Anweisungen sind natürlich Kokolores. Wahrscheinlich sind die mir da 'reingerutscht, weil ich mit dem Projektmanager beim Anlegen der neuen Version nicht aufgepasst hab ;-). Einfach beide Zeilen löschen und durch
.include "m8515def.inc"
ersetzen. Dann klappt's wirklich... :-).
Geniess Ostern!
mare_crisium
robo_wolf
07.04.2010, 08:10
mare_crisium,
will mich heute endlich mal wieder melden.
Habe nun Deinen Code schon mehrmals durch den Simulator getickert.
Leider ist bei mir der Groschen noch immer nicht am Boden angekommen.
Aber ich arbeite dran... ;.-)
Kann aber auch noch nicht 100%ig sagen woran das genau liegt.
Eventuell seh ich auch nur den Wald vor lauter Baeumen nicht.
Bin gerade dabei den Ablauf aus dem Code mit meinen Worten heraus zu schreiben, um Ihn letzlich komplett zu verinnerlichen.
Versuche bis heute Abend damit fertig zu sein.
mare_crisium
07.04.2010, 09:25
robo_wolf,
nur nicht hetzen :-) ! Ich habe ja auch ziemlich viel daran herumgetüftelt.
Angefangen hatte ich mit dem naheliegenden Ansatz: Einem Algorithmus, der brav die verschiedenen Stadien des Einschaltens der Menusteuerung und später das Verhalten im eingeschalteten Zustand durchbuchstabiert.
Diese Lösung gefiel mir aber nicht, weil sie in einen Wust von Fallunterscheidungen ausuferte. Besonders verwirrend fand ich, dass während des Einschaltvorgangs einmal die Tastenflanke das auslösende Ereignis ist, und dann wieder das Ablaufen eines Warteintervalls. Auch die Zeitintervalle, die überwacht werden müssen, unterscheiden sich, abhängig vom Zustand der Menusteuerung: Beim Einschalten sind es T0, T1 und T2. Nachdem die Steuerung eingeschaltet ist, braucht man nur noch die Totmannzeit T2 überwachen.
Wie sich nach einigem Herumprobieren herausstellte, wird das Ganze übersichtlicher, wenn man die Zeitmessung (T0 -> T1 ->T2) immer gleich ablaufen lässt; sie also davon unabhängig macht, ob die Menusteuerung eingeschaltet ist oder nicht. Ganz entscheidend ist es, die Zeitmessung bei jeder Tastenflanke neu zu starten: Eine neue Tasteflanke wird dann durch das Zurückspringen des Zeitzählers auf Null angezeigt, bzw. dadurch, dass T0 (oder T1) abläuft. Dadurch braucht man nur noch die Zeitmessung zu überwachen, nicht die Tastenflanken.
Das ist die wesentliche Vereinfachung ;-).
Um festzustellen, was der Bediener mit seiner Eingabe bezweckte, braucht man nur noch bei T0 bzw. T1 den statischen (nicht den dynamischen) Zustand der Tasten abfragen: Die Tatsache, dass T0 bzw. T1 erreicht werden, zeigt ja schon an, dass mindestens eine der Tasten neu betätigt wurde. Bei Erreichen von T2 geht's immer auf direktem Weg zurück nach "Los" (Schrittnummer 0x00).
Viel Spass beim Nachtüfteln :-).
Ciao,
mare_crisium
robo_wolf
07.04.2010, 20:18
Hallo mare_crisium,
je intensiver ich mich damit beschaeftige, um so mehr bewegt sich der Groschen dem Boden. ;-)
... aber der Kopf qualmt schon ein wenig... - macht aber nichts -
Meine Frau liest einen Krimi und finde es eben spannend mich durch den Code zu arbeiten.
Trotzdem bitte ich Dich mal das Untere zu lesen und ggf. zu korrigieren.
- bin aber noch nicht ganz fertig, mit dem Durcharbeiten und Verstehen -
Den kompletten "MNU8CTL_HANDLER" habe ich noch nicht durch...
Frage zum Ablauf:
In der Doku auf Seite 1 "Jedes Menu hat 8 Menupunkte, die durch Betätigen eines einzelnen Tasters
(Taste0 bis Taste7) ausgewählt werden."
-->> die Idee war ein Menue, in dem 8 Menuepunkte erkannt werden koennen. Meintest Du es so? oder meinst Du 8 Menues mit 8 Menuepunkten?
Doku mare_crisium(wie ich es verstanden habe - bitte verbessere mich, wenn es anders gemeint war):
A.0 Abfrage auf Ta0 oder Ta7 log 1
Rueckgabe 0 -> A.0
Rueckgabe 1 -> A.1
A.1 1.Abfrage auf Ta0 oder Ta7 log 1
Rueckgabe 0 -> A.0
Rueckgabe 1 -> 2. Abfrage
2.Abfrage auf Ta0 oder Ta7 log 1 nach Ablauf von T1
Rueckgabe 0 -> A.0
Rueckgabe 1 -> A.2
A.2 Abfrage aller Tasten(0-7)
Rueckgabe := Tasten-ID
Rueckgabe mehrere Tasten IDs = Fehlerfall ->Signalisierung + Start von A.2
T2 abgelaufen -> A.0
ICALL hatte ich bis jetzt noch nie verwendet - nun weiss ich, was es damit auf sich hat. :-)
############# meine Gedankenstuetze
Ablauf der Menuesteuerung nach mare_crisium
Bsp.:
Datenspeicher(Datensegement)
MNU8CTL = RAM_Anfang = 0x60
MNU8CTL_LNG = 12 = 0x0C (Byte im Datenspeicher reserviert)
MNU8CTL_TBL_LNG = 16 = 0x10 (Laenge der Zustandstabelle)
aktuelle Zustandsnummer der Menusteuerung
MNU8CTL_FLAGS = 0 (Flags)
MNU8CTL_AKTZUST = 1 (aktueller Zustand)
MNU8CTL_TASTZUST = 2 (Tastenzustand)
MNU8CTL_ZYTZHLRL = 3 (Zaehler-Adresse-Low)
MNU8CTL_ZYTZHLRH = 4 (Zaehleradresse -High)
MNU8CTL_TBLADRL = 5 (Adresse Zustandstabelle -Low)
MNU8CTL_TBLADRH = 6 (Adresse Zustandstabelle -High)
MNU8CTL_MAXNR = 7 (maximale Anzahl Datensaetz in Zustandstabelle)
-----------------------------------------------------------------------------------------------------------------------------------
0x60 0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68 0x69 0x6A 0x6B
-----------------------------------------------------------------------------------------------------------------------------------
y = MNU8CTL X
-----------------------------------------------------------------------------------------------------------------------------------
y+MNU8CTL_LNG X
-----------------------------------------------------------------------------------------------------------------------------------
y+MNU8CTL_FLAGS X
-----------------------------------------------------------------------------------------------------------------------------------
y+MNU8CTL_AKTZUST X
-----------------------------------------------------------------------------------------------------------------------------------
y+MNU8CTL_TASTZUST X
-----------------------------------------------------------------------------------------------------------------------------------
y+MNU8CTL_ZYTZHLRL X
-----------------------------------------------------------------------------------------------------------------------------------
y+MNU8CTL_ZYTZHLRH X
-----------------------------------------------------------------------------------------------------------------------------------
y+MNU8CTL_TBLADRL X
-----------------------------------------------------------------------------------------------------------------------------------
y+MNU8CTL_TBLADRH X
-----------------------------------------------------------------------------------------------------------------------------------
y+MNU8CTL_MAXNR X
-----------------------------------------------------------------------------------------------------------------------------------
reserve X X X X
-----------------------------------------------------------------------------------------------------------------------------------
MNU8CTL_CREATE:
im Datenspeicher ab 0x60 mit einer Laenge von MNU8CTL_LNG :=16 Byte reservieren und mit 0 fuellen
in Speichertstelle y+MNU8CTL_MAXNR(0x67) die Anzahl der max. Zustaende speichern
in Speicherstellen y+MNU8CTL_TBLADRL und y+MNU8CTL_TBLADRH den Zeiger auf die Zustandstabelle speichern(0x65 und 0x66)
MNU8CTL_ZYTRESET:
im Datenspeicher an y+MNU8CTL_ZYTZHLRL(0x63) und y+MNU8CTL_ZYTZHLRH(0x64) den Wert von T0 speichern
MNU8CTL_SETTASTEN:
im Datenspeicher an y+MNU8CTL_TASTZUST(0x62) die statischen( simulieret Tasten) Zustaende der Tasten speichern
MNU8CTL_ZYTCOUNT:
aus Datenspeicher an y+MNU8CTL_ZYTZHLRL(0x63) und y+MNU8CTL_ZYTZHLRH(0x64) den Wert von T0 lesen
MU8CT_ZTCT04:
im Datenspeicher an y+MNU8CTL_ZYTZHLRL(0x63) und y+MNU8CTL_ZYTZHLRH(0x64) den decr. Wert von T0 speichern
MNU8CTL_HANDLER:
1.Byte low =(0x18) und 2.Byte high =(0x01) aus Zustandstabelle lesen - Zeiger auf die auszuefuehrende Prozedur (icall)???? - zusammen das DoppelByte 0x0118
3.Byte low =(0x00) und 4.Byte high =(0x00) aus Zustandstabelle lesen - Folgezustand
icall ruft Prozedur welche an FlashAdresse (0x0118) vom Z-Zeiger auf
bis zum naechsten MAIN-Aufruf
##################
mare_crisium
07.04.2010, 22:21
robo_wolf,
na, ich schliesse aus Deinen Fragen, dass der Groschen nicht mehr weit hat :-) !
Ja, bei der Beschreibung des Übergangskriteriums von A.0 nach A.1 habe ich in der pdf-Datei einen Fehler gemacht und eine Erklärung unterschlagen. Beides habe ich in der Version _V09 ausgebessert.
Zuerst ist das Kriterium für A.0->A.1 die Tatsache, dass von Ta0 oder Ta7 mindestens eine Tastenflanke ansteht (nicht, wie in Version _V08 geschrieben "keine der beiden"). Nachdem mir aber die Idee gekommen war, die Zeitmessung bei jeder Tastenflanke neu zu starten, habe ich das Übergangskriterium modifiziert, ohne das in der doku deutlich zu machen (ist jetzt in _V09 ergänzt). Und zwar genügt es ja, wenn die Zeitmessung neu begonnen und T0 erreicht hat, zu überprüfen, ob zu diesem Zeitpunkt Ta0 und Ta7 betätigt sind. Wenn das nämlich so ist, dann wurde eine von beiden zur Zeit t=-T0 neu betätigt. Fehlt zu diesem Zeitpunkt eine von beiden, oder sind ausser den beiden noch andere Tasten betätigt, geht's zurück nach A.0.
Deine Aufstellung muss also so aussehen:
A.0 Abfrage auf Ta0 und Ta7 nach Ablauf von T0 log. 1
Resultat "Nein" -> A.0
Resultat "Ja" -> A.1
A.1 Abfrage auf Ta0 und Ta7 nach Ablauf von T1 log. 1
Resultat "Nein" -> A.0
Resultat "Ja" -> A.2
Die Abfrage in A.2 hast Du so richtig dargestellt. Auch die Kurzbeschreibungen der Prozeduren ist so richtig. Nur bei MNU8CTL_ZYTCOUNT wird nicht nur der Wert von T0 heruntergezählt sondern auch der von (T1-T0) und der von (T2-T1). Bei MNU8CTL_HANDLER wird der Zeiger auf die auszuführende Prozedur aus den ersten beiden Bytes nach zh:zl geladen und dann mit der "icall"-Anweisung angesprungen, genau so, wie Du's beschreibst. Die Prozedur entscheidet mit dem Wert, den sie im T-Flag zurückgibt, welcher Folgezustand eingestellt werden soll. Das hast Du auch richtig.
Ciao,
mare_crisium
robo_wolf
10.04.2010, 17:57
mare_crisium,
es geht immer noch langsam, aber weiter... :-)
schau mal, das kann doch nicht stimmen?
[Code]
MU8CTL_ZTCT02:
; ZYTZUST > 1
cpi r18,0x02 ;ist ZYTZUST = 2?
brne MU8CTL_ZTCT03
[Code/]
muss es nicht so heissen?
[Code]
MU8CTL_ZTCT02:
; ZYTZUST > 1
cpi r16,0x02 ;ist ZYTZUST = 2?
brne MU8CTL_ZTCT03
[Code/]
habe mal den kompletten Ablauf ohne Eingriff bzw. Bit-Manipulation aufgeschrieben(in Main "ser r16":
######
keine Taste gedrueckt:
| A.0
V
Zaehler an Adresse:(y+MNU8CTL_ZYTZHLRL) wird von 0x03 auf 0x00 herab gezaehlt
-> aktueller Zustand an Adresse:(y+MNU8CTL_AKTZUST) von 0x00(Zyt 0) auf 0x01(Zyt 1) gesetzt und Zaehler an Adresse:(y+MNU8CTL_ZYTZHLRL) auf T1-T0 = 5-3 = 0x02 gesetzt
| A.1
V
Zaehler an Adresse:(y+MNU8CTL_ZYTZHLRL) wird von 0x02 auf 0x00 herab gezaehlt
-> aktueller Zustand an Adresse (y+MNU8CTL_AKTZUST) von 0x01(Zyt 1) auf 0x02(Zyt 2) gesetzt und Zaehler an Adresse:(y+MNU8CTL_ZYTZHLRL) auf T2-T1 = 8-5 = 0x03 gesetzt
| A.2
V
Zaehler an Adresse:(y+MNU8CTL_ZYTZHLRL) wird von 0x03 auf 0x00 herab gezaehlt
-> aktueller Zustand an Adresse (y+MNU8CTL_AKTZUST) von 0x03(Zyt 3) wird erneut mit 0x03 geladen
|
V
Totmannzeit erreicht - Zustaende bleiben nun fix
######
// bin ich noch auf den richtigen Weg?
als Naechstes moechte ich in der Main r16 mit "0x01", "0x02", "0x80", "0x81", "0x82" laden...
mare_crisium
11.04.2010, 21:11
robo_wolf,
ja, die Anweisung muss "cpi r16,0x02" heissen, nicht "cpi r18,0x02". Hab' die korrigierte Datei neu hochgeladen.
Den Ablauf, den Du in Deinem Posting darstellst, bekomme ich im Simulator auch. Er belässt den Zustand der Menusteuerung bei "0x00", weil ausser Ta0 und Ta7 noch mehr Tasten betätigt sind (simuliert). Am Ende landet der Ablauf im Zustand 0x03, d.h. Totmannzeit abgelaufen. Von hier aus geht's nur weiter, wenn eine neue Tastenflanke ansteht.
Ja, bist noch auf der richtigen Fährte :-) !
Ciao,
mare_crisium
robo_wolf
12.04.2010, 21:34
mare_crisium,
die Menu8Stg habe ich so weit verstanden.
Aber warum laesst Du den Code immer bis zur Totmannzeit laufen?
Wenn keine Taste gedrueckt ist, kann der Code doch schon vorher abgefangen werden.
Was spricht gegen diesen Ablauf:
############
keine Tastenflanke erkannt (T-Flag nicht gesetzt) / A.0
! ->
keine Erkennung von Tasten 0 und 7 / kein A.1
Tastenflanke erkannt (T-Flag gesetzt)
! ->
Erkennung ob Taste 0 und 7 (0x81) anstehen / A.1
in der Zeitnachfuehrung immer wieder auf (0x81)ueberpruefen
-> wenn (0x81) vor Ablauf der Zeitnachfuehrung nicht mehr ansteht -> dann wieder zuzrueck zu A.0
-> wenn (0x81) am Ende der Zeitnachfuehrung noch antsteht -> dann in Tastenauswertung fuer Menuepunkt A.2
! ->
Tasten fuer Menuepunkte erkennen / A.2
in der Zeitnachfuehrung auf Tasten testen
-> wenn vor Ablauf der Totmannzeit keine einzelne Taste erkannt wurde fuer Menuepunkt erkannt wurde -> dann wieder zurueck zu A.0
-> Tasten erkannt -> "Menuepunkt gestetzt" -> dann zureuck zu A.0
############
Aber wahrscheinlich hast Du hier in Deinen Gedanken schon viel weiter gedacht als ich es ueberschaue... :-)
Du hast sicher nicht ohne Grund Platz fuer Erweiterungen gealssen ... ODER?
Aber wieder viel dazu gelernt und das ist wichtig,. ......wenn es auch etwas laenger gedauert hat.
Das Arbeiten mit Zeigern und das Verwenden des SRAM und Flash ist mir nun, Dank Deiner Hilfe, nicht mehr so fremd.
mare_crisium
13.04.2010, 07:40
Guten Morgen, robo_wolf,
ja, es hat mich beim Schreiben des Programms auch öfter in den Fingern gejuckt, den Zeitzähler früher wieder zurückzusetzen. - Aber: Wenn keine oder nur ungültige Tasten betätigt werden, dann ist es nur logisch, den Automaten bis zum Zustand "Totmannzeit erreicht" laufen zu lassen. Denn genau das ist es ja, was passiert ist: Es gab keine rechtzeitige gültige Eingabe. - Ausserdem ist es einfacher, wenn es nur ein Ereignis gibt, das den Zeitzähler zurücksetzt.
Aber mein Programmentwurf ist nicht er einzig mögliche und sicher auch nicht der Beste ;-) ! Deshalb: Lass' mal die Finger wirbeln und versuch's mal so zu machen, wie Dir's vorschwebt! Der Vergleich konkurrierender Programmentwürfe ist eine sehr wichtige Quelle für Programmiererfahrung - besonders, wenn man die Entwürfe selbst geschrieben hat :-). Also, keine Hemmungen, meiner Mithilfe kannst Du gewiss sein.
Das Arbeiten mit Zeigern und das Verwenden des SRAM und Flash ist mir nun, Dank Deiner Hilfe, nicht mehr so fremd.
Darauf kam's mir an! Freut mich :-) .
Ciao,
mare_crisium
robo_wolf
15.04.2010, 22:00
Guten Abend mare_crisium,
probiere schon in so mancher freien Minute, den Code so umzuschreiben, dass es meinen Vorstellungen entspricht. ... dauert noch :-)
Dabei ist eine Frage aufgetaucht:
Das Simulieren der Tastenflanke geschieht mittels T-Flag.
Damit wird ein Zeit-Reset gemacht oder man kommt damit wieder aus der Totzeit raus.
Aber...
eine Tastenflanke ensteht auch beim Druecken der Tastenkombi"0_7" ..
--- Im momentanen ProgrammCode wuerde immer ein Reset stattfinden.
Oder simulierst Du die Zustaende anders? Auch komme ich nicht in MNU8CTL_CHKTASTEN beim simulieren.
Kannst Du mir noch einen Tipp geben?
mare_crisium
16.04.2010, 12:18
Guten Morgen, robo_wolf,
ja, die Tastenflanke setzt immer nur den Zeitzähler zurück. -
Aber gehen wir doch mal den Fall „Ta0 und Ta7 betätigt“ durch. Dabei mache ich's wie Du und verwende für die Zustände der Menusteuerung (Bits2 und 3 des Zustandsbytes) statt der 0b00 den Buchstaben A, statt 0b01 das B usw. - Also, los geht's:
Angenommen, Ta0 wird zuerst betätigt. Die Tastenflanke (T=1) setzt den Zeitzähler zurück, der neue Zustand ist A.0.
Der Zeitzähler erreicht T0, bevor Ta7 betätigt wird: Die Prüfung des statischen Tastenzustands (CHKT07) liefert ein "Nein" (T=0). Der neue Zustand der Menusteuerung bleibt A. Der Zeitzähler läuft weiter, die Zustände laufen über A.1 bis A.3. Im Zustand A.3 bleibt bis eine neue Tastenflanke kommt.
Jetzt wird Ta7 betätigt. Die Menusteuerung ist noch in Zustand A.X. Der Zeitzähler springt auf 0 zurück und fängt an, T0 abzuzählen; der neue Zustand ist A.0.
Wenn er jetzt T0 erreicht, liefert die Prüfung des Tastenzustandes mit CHKT07 ein „Ja“ (T=1) und - voila - die Menusteuerung springt in den Zustand B.1. Der Zähler wird mit der Differenz (T1-T0) geladen und fängt an abzuzählen.
Wird jetzt noch eine Taste betätigt. Der Zeitzähler wird zurückgesetzt, der neue Zustand ist B.0. Erreicht der Zeitzähler T0, dann liefert die Tastenprüfung ein „Nein“, weil mehr als Ta0 und Ta7 betätigt sind, der neue Zustand ist A.1 und die Steuerung läuft weiter bis A.3.
Wird keine weitere Taste betätigt, dafür aber Ta0 oder Ta7 losgelassen, dann liefert die Prüfung nach Ablauf von T1 (Zustand B.1) ein „Nein“ und wir landen in Zustand A.2 und die Reise geht weiter bis A.3.
Wenn aber beim Erreichen von T1 sowohl Ta0 als auch Ta7 weiterhin betätigt sind, dann liefert CHKT07 ein „Ja“ und die Steuerung geht in den Zustand C.2 über. Ab jetzt wartet sie auf Eingaben, die den gewünschten Menupunkt auswählen.
Angenommen, Ta2 wird vor Erreichen der Totmannzeit betätigt. Das löst eine Tastenflanke aus, die den Zeitzähler wieder zurückgesetzt (das hast Du, glaub' ich, in Deiner Simulation vergessen ;-) ). Der neue Zustand ist C.0. Nach Ablauf von T0 wird der statische Tastenzustand geprüft (CHK_TASTEN). Je nachdem was dabei herauskommt muss jetzt die gewünschte Aktivität angestossen werden. Der Zeitzähler läuft derweil weiter. Wenn er die Totmannzeit erreicht, geht die Steuerung in den Zustand A.3 über und wartet auf die nächste Tastenflanke.
Alles klar?
mare_crisium
robo_wolf
16.04.2010, 15:26
mare_crisium,
wie Du editiert die Bits 2 und 3 des Zustandbyte bei der Simulation?
Sollten die Zustaende nicht selbst errechnet bzw. vom PC zugewiesen werden?
Die Simulation fuer die Ta0 und T7 Erkennung hatte ich bisher so gestartet:
Beim Initialisieren alle Prozeduren einmal komplett durch
Der RAM schaut dann so aus:
00 00 FF 02 00 9C 00 10
Dann setzen ich fuer Ta0 und Ta7 in der MAIN r16 auf 0x81 setze das T-Flag und los.
00 00 81 03 00 9C 00 10
Erwarten wuerde ich dann vom Programm das es bis zu CHK_Tasten kommt...
... ich bin auf dem Holzweg ODER???
hier noch einmal in einer anderen Darstellung den gewuenschten Programmablauf:
>>> Zeitstrang >>>
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
>>> Tastenflanke >>>
<<< nicht erkannt
>>>>>>>>> erkannt >>>>>>>>>>>>>>>>>>>>>>>>
-------------->>> Abfrage auf Ta0 und Ta7
<<<<<<<<<<<<<<<<< nicht erkannt
-------------->>> erkannt>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<< kuerzer 3 Zeiteinheiten
-------------->>> erkannt 3 Zeiteinheiten >>>>>>>>>>>>>>>>>>>>>> Menue erreicht
------------------------------------------------------------------>>> Totzeit 5 Zeiteinheiten gestartet >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Ta0 und Ta7 nicht losgelassen <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Totmannzeit abgelaufen <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
------------------------------------------------------------------>>> Einzeltaste erkannt >>>>
------------------------------------------------------------------------------------------>>>> Menuepunkt gewaehlt
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
mare_crisium
16.04.2010, 21:56
robo_wolf,
nee, nee Du bist schon richtig ;-) ! Gehen wir anhand des RAM vor:
Wie Du schreibst, steht im RAM nach der Tatenflanke und wenn Ta0 und Ta7 betätigt sind
00 00 81 03 00 9C
So, jetzt musst Du das Programm einfach durchlaufen lassen. Es geht dann von selbst in den Zustand B.1 über. Im RAM steht dann
00 05 81 02 00 9C
05 ist das Zustandsbyte = 0b0101. Das bedeutet: Zeitzähler hat bis T0 gezählt. Deshalb steht in Bits1 und 0 die 0b01. Die Menusteuerung (Bits3 und 2) ist im Zustand 01. In Deiner Nomenklatur ist das Zustand B.1. Das Tastenbyte 0x81 bedeutet, dass weiterhin Ta0 und Ta7 betätigt sind.
Lass' das Programm einfach weiterlaufen. Der Zeitzähler zählt die Differenz T1-T0 ab. Wenn er die Null erreicht, wird er mit 0x03 nachgeladen. Bevor die Anweisung "rcall MNU8CTL_HANDLER" ausgeführt wird, steht nun im RAM
00 06 81 03 00 9C
Das Zustandsbyte ist jetzt 0x06, das entspricht B.2. 0x81 bedeutet, dass Ta0 und Ta7 immer noch betätigt sind. Jetzt wird die Anweisung
"rcall MNU8CTL_HANDLER" ausgeführt. Dabei wird "MNU8CTL_CHKT07" aufgerufen. Weil Ta0 und Ta7 noch immer betätigt sind, geht die Menusteuerung in den Zustand 2, also C über. Der RAM-Inhalt ist deshalb
00 0A 81 03 00 9C
Das Zustandsbyte ist 0x0A. Bits3 und 2 enthalten 0b10, also ist die Menusteuerung im Zustand 2 = C. Der Zeitzähler ist gerade mit derm Abzählen von T1 fertiggeworden, also steht in Bits1 und 0 die 0b10. Er fängt jetzt an, die Differenz T2-T1 abzuzählen.
Das ist genau der Zustand, in dem die Ta0 und Ta7 wieder losgelassen werden dürfen und Menu-Auswahl-Taste betätigt werden muss. Sagen wir mal, Ta2 wird betätigt. Also in der Simulation das T-Flag setzen (es wurde ja eine neue Taste betätigt, das Flankenbit steht also an) und in r16 musst Du 0x02 schreiben.
Wg. der Tastenflanke führt das Programm die Anweisung "rcall MNU8CTL_ZYTRESET" aus. Nachdem auch "rcall MNU8CTL_HANDLER" ausgeführt ist, steht im RAM
00 08 02 03 00 9C
Das Zustandsbyte ist 0x08, was dem Zustand C.0 entspricht. Das Programm läuft weiter bis T0 abgezählt ist, Im RAM steht jetzt
00 09 02 00 00 9C
Anschliessend wird vom "MNU8CTL_HANDLER" die Prozedur "MNU8CTL_CHKTASTEN" aufgerufen und die gewünschte Aktion angestossen. Im RAM steht danach
00 09 02 02 00 9C
So weit einverstanden ;-) ?
mare_crisium
robo_wolf
17.04.2010, 06:25
Guten Morgen mare_crisium,
jetzt ist alles klar.
Wir sind teilweise unterschiedlicher Meinungen bezueglich der Tastenflanke gewesen. Ausserdem habe ich den nachfolgenden Zustand von 0x0A nicht bedacht. Ta0 und Ta7 3 sec erkannt - aber weiter gedrueckt gehalten. Habe hier auch das Posting vorher erweitert.
robo_wolf
24.04.2010, 07:14
Guten Morgen mare_crisium,
habe nun endlich den Code so weit, dass ich ihn posten kann.
Aenderungen:
Ohne Tastenflanke - kein kompletter Durchlauf bis zur Totmannzeit
Einzeltastenabfrage zur Erkennung, ob nur eine Taste zur Auswahl ansteht.
Nach Tastenerkennung in CHKTASTEN ist jetzt eine Programmunterbrechnung eingebaut, die auf Anfangsbedingungen setzt.
Ergebnisaufnahme in Datensegment zur weiteren Verarbeitung abgelegt.
Menuesignalisierung eingepflegt-tut aber noch nichts.
Wenn ich mir das Programm so anschaue, bin ich nicht 100ig zufrieden damit.
Es gibt noch viel Optimierungspotential... :-(
robo_wolf
25.04.2010, 18:24
mare_crisium,
wie ich schon geschrieben habe, mache ich mir noch weitere Gedanken zur Verbesserung.
Was haelst du davon, bespielsweise die Einzeltastenabfrage und /oder die Abbruchfunktion mit in die Tabelle einzubauen?
Kann man die Spalteneintraege in 3 oder gar mehr erweitern.. die natuerlich dann in dem Handler mit ansprechen?
z.B.:
.dw .dw MNU8CTL_NOP, 0x0000 ; MNU8CTL_TU_WAS; 0 ->
Hatte mir auch in die Richtung Gedanken gemacht:
Ein Umstricken der Tabelle und den zeitlichen Verlauf mit einbauen(bissel sehr gewagt - ich weiss)
.dw MNU8CTL_NOP, 0x0000 ; 0 -> Tastenflanke erkannt
; Menusteuerung im Zustand 1 -> 3Zeiteinheiten
.dw MNU8CTL_CHKT07, 0x0001 ; 1 ->
.dw MNU8CTL_CHKT07, 0x0001 ; 2 ->
.dw MNU8CTL_CHKT07, 0x0011 ; 3 -> MenueEinstieg erreicht
; Menusteuerung im Zustand 2 -> 5Zeiteinheiten
.dw MNU8CTL_CHKTASTEN, 0x0002 ; 4 ->
.dw MNU8CTL_CHKTASTEN, 0x0002 ; 5 ->
.dw MNU8CTL_CHKTASTEN, 0x0002 ; 6 ->
.dw MNU8CTL_CHKTASTEN, 0x0002 ; 7 ->
.dw MNU8CTL_CHKTASTEN, 0x0022 ; 8 -> Totmannzeit erreicht
; Menusteuerung im Zustand x (vorerst unbenutzt)
.dw MNU8CTL_NOP, 0x0000 ; 9 ->
.dw MNU8CTL_NOP, 0x0000 ; 10 ->
.dw MNU8CTL_NOP, 0x0000 ; 11 ->
.dw MNU8CTL_NOP, 0x0000 ; 12 ->
.dw MNU8CTL_NOP, 0x0000 ; 13 ->
.dw MNU8CTL_NOP, 0x0000 ; 14 ->
.dw MNU8CTL_NOP, 0x0000 ; 15 ->
.equ MNU8CTL_TBL_LNG = 16 ;
mare_crisium
25.04.2010, 23:07
robo_wolf,
hab's mir gerade runtergeladen, melde mich demnächst.
Ciao,
mare_crisium
mare_crisium
28.04.2010, 01:18
robo_wolf,
so, hier sind meine Kommentare zu Deinem Programm. Mein Eindruck ist, dass Du ruhig die Zustandstabelle ändern solltest ;-) . Ich hab' Dir ein paar Vorschläge dazu in den Quelltext geschrieben.
Ciao,
mare_crisium
robo_wolf
28.04.2010, 06:22
Guteen Morgen mare_crisium,
werde ich gleich mal durcharbeiten.
Vielen Dank ersteinmal.
robo_wolf
30.04.2010, 16:17
Hallo mare_crisium,
Deine Kritiken den Programmabbruch habe ich verstanden.
Im fogenden Kommentar schreibst Du, das Nachteil sei, nur eine Tastennummer zu speichern.
??? Warum sollten mehrere Tastennummern abgelegt werden? Fuer die Menueauswahl brauche ich doch nur eine Nummern ,die die Aktion der Tasten bestimmt?
!!! Denken wir da in zwei Richtungen.. oder ueberseh ich wieder etwas..?
Das Thema mit der Warteschlange liest sich spannend.
- kommen garantiert noch mehr Fragen auf Dich zu :-)
Auch der neue Handler "MNU8CTL_4HANDLER" gefaellt mir.
mare_crisium
02.05.2010, 16:21
Guten Abend, robo_wolf,
hier habe ich eine neue Version _04j, die aus _04i abgeleitet ist. Sie hat jetzt ein neues Modul "STACK8_V01.asm", das Software-Stacks bereitstellt. Man kann in seinem Hauptprogramm mehrere davon einbauen.
In _04j dient der "MENUSTACK" dazu, die Nummern der betätigten Menutasten zwischenzuspeichern. Die Gründe dafür sind folgende:
Man kann die Verarbeitung der Menutasten in die MAIN-Schleife verlagern und braucht nicht alles während des Timer-Interrupts zu erledigen. Ausserdem kommt es oft vor, dass jemand die Menutasten so schnell hintereinander betätigt, dass die erste noch nicht abgearbeitet ist, wenn die zweite gedrückt wird. Ohne Zwischenspeicherung ginge die zweite verloren. Noch ein anderer Grund ist, dass auf diese Weise die Verarbeitung der Menutasten von der Menusteuerung abgekoppelt wird. Das Modul "MENU8STRG_Vxx" braucht nichts über die Funktionen zu wissen, die die Tasten auslösen. Das Modul bleibt dadurch universell verwendbar ;-) .
Ciao,
mare_crisium
mare_crisium
02.05.2010, 18:15
robo_wolf,
nach der ganzen Testerei wird's mal wieder Zeit, das eigentliche Ziel ins Auge zu fassen :-). Deshalb habe ich hier eine Version (LernPrgrm_04k) angehängt, die eigentlich alles können sollte: Tasten entprellen, die Menuaktivierung steuern und die angewählten Menupunkte ausführen.
Dazu habe ich den Timer2-Interrupt wieder aktiviert. Die Zeitkonstante habe ich von Dir übernommen (0,1s). Die Zeitintervalle für die Menuaktivierung habe ich provisorisch auf
T0 = 0,1s, T1 = 3s und T2 (Totmannzeit) = 5,1s eingestellt.
Wenn wir beim Austesten alle Fehler bemerkt und beseitigt haben ;-) müsste das Ding funktionieren... Probier's doch mal!
Ciao,
mare_crisium
robo_wolf
02.05.2010, 18:26
Hallo mare_crisium,
langsam langsam :-)
Bin gerade noch beim Durchsehen des vorherigen Postings... da kommt schon das Naechste.
Im vorigen hattest Du .include "Menu8Strg_V04.asm" eingebaut.
Das war sicher ein Tippfehler.
In der Endversion ist es dann wieder _V03.
Auf jeden Fall erst einmal wieder ein grosses Danke an Dich zurueck.
Habe nun einiges zum Aufarbeiten .. :-)
robo_wolf
04.05.2010, 11:46
mare_crisium,
bin nun Dein Programm durchgegangen.
Eine Sache ist mir gleich zu Beginn aufgefallen.
Der Mega8515 hat keinen Timer2.
In irgendeiner Version der letzten Postings hast Du die Interrupttabelle vom Mega8 eingebaut.
Muss ehrlich gestehen, ich habe das in Deinem letzten Posting zwar gelesen(Dazu habe ich den Timer2-Interrupt wieder aktiviert.),
mehr ueberlesen, aber beim 1.TimerCompare ist es mir dann aufgefallen.
Wenn sich Stoplersteine immer so leicht finden lassen wuerden.... :-)
Eine kleine Aenderung habe ich dann noch hier vorgenommen. Bringt nicht wirklich viel.. ein paar Takte
STACK8_CREATE:
push r25
in r25,SREG
push r16
push r17
push xl
push xh
; reservierten Speicherbereich mit Nullen füllen
mov xl,r16
clr xh
adiw xh:xl,STACK8CTL_LNG
clr r17
STCK8_CRT00:
; 20100503 vor STCK8_CRT00 geschoben clr r17
st z+,r17
sbiw xh:xl,0x0001 ; Byte-Zähler abzählen
brne STCK8_CRT00 ; Sprung, wenn noch nicht fertig
; Zeiger wieder auf erstes Byte von STACK8CTL zurückstellen
sbiw zh:zl,STACK8CTL_LNG ; Länge des Steuerblocks subtrahieren
sub zl,r16
clr r17
sbc zh,r17 ; Kapazität des Stacks subtrahieren
; Stackkapazität speichern
std z+STACK8CTL_CAP,r16 ; Kapazität speichern
pop xh
pop xl
pop r17
pop r16
out SREG,r25
pop r25
ret
Der MenuStack ist ja ne schoene Sache. Aber waere es nicht einfacher die Tasterkennung in einem Register dezimal abzulegen?
Was wuerde dagegen sprechen? - Ich meine als Lernobjekt ist die Sache mit dem Stack ja suuuuper.
... kann es natuerlich sein, dass Du in Deinem Hinterkopf schon weiter geplant hast, als ich es bisher ueberschauen kann?
mare_crisium
04.05.2010, 15:38
robo_wolf,
huuch! Das mit dem Timer2 ist natürlich eine böse Falle. Ich hätte erwartet, dass der Assembler auf so einen groben Fehler hinweist. Wie machst Du's jetzt?
Deine Einsparmassnahme beim Stack ist berechtigt. Aber was meinst Du mit
in einem Register dezimal abzulegen
Man kann nie vohersagen, wann oder wie schnell hintereinander der Bediener die Tasten betätigt ;-) . Legt man die Nummer der betätigten Taste in einem Register ab, dann kann es vorkommen, dass ein ultrahibbeliger Bediener schon die nächste drückt, bevor die erste überhaupt ausgewertet ist. Dann wird der Inhalt des Registers mit der Nummer der zuletzt betätigten Taste überschrieben und die erste Eingabe war für die Katz. Sehr frustrierend für den Bediener ;-) ! Den Puffer betrachte ich hier als nützlich, weil es zu vermeiden gilt, dass nicht synchronisierbare Eingaben verloren gehen.
Freut mich, das der Software-Stack Dir so gut gefällt :-). Gedacht war er auch, um Dir auf solche Schmankerl Appetit zu machen. Denn mein Vorschlag ist nun, dass Du als Nächstes mal versuchst, einen FIFO-Speicher zu bauen. Speichern soll er einzelne Bytes; die Kapazität soll einstellbar sein und bis zu 255 gehen dürfen. Es sollen mehrere FIFOs in einem Programm einzurichten sein. Natürlich soll alles in einem wiederverwendbaren Modul verpackt werden. Es soll die Prozeduren
FIFO8_CREATE
Eingangsparameter:
zh:zl enthalten Zeiger auf den Beginn des Steuerblocks
r16 enthält die gewünschte Kapazität in Byte
Ausgangsparameter:
zh:zl unverändert
r16 unverändert
FIFO8_WRITE
Eingangsparameter:
zh:zl enthalten Zeiger auf den Beginn des Steuerblocks
r16 enthält das Datenbyte, das auf der FIFO abgelegt werden soll
Ausgangsparameter:
zh:zl unverändert
r16 unverändert
SREG (T-Flag): T = 0 Fehlschlag, FIFO war voll
T=1 Datenbyte erfolgreich auf der FIFO abgelegt
FIFO8_READ
Eingangsparameter:
zh:zl enthalten Zeiger auf den Beginn des Steuerblocks
Ausgangsparameter:
zh:zl unverändert
r16 enthält das Datenbyte, das aus der FIFO ausgelesen wurde
SREG (T-Flag): T = 0 Fehlschlag, FIFO war leer
T=1 Datenbyte erfolgreich aus der FIFO ausgelesen
FIFO8_ISTLEER
Eingangsparameter:
zh:zl enthalten Zeiger auf den Beginn des Steuerblocks
Ausgangsparameter:
zh:zl unverändert
SREG (T-Flag): T = 0 FIFO ist nicht leer
T=1 FIFO ist leer
FIFO8_ISTVOLL
Eingangsparameter:
zh:zl enthalten Zeiger auf den Beginn des Steuerblocks
Ausgangsparameter:
zh:zl unverändert
SREG (T-Flag): T = 0 FIFO ist nicht voll
T=1 FIFO ist voll
exportieren. Als Muster, hatte dich mir gedacht, könntest Du den STACK8 verwenden; allerdings ist die FIFO etwas komplizierter, denn da es sind ein paar mehr Fälle zu berücksichtigen.
Mit so einem Ding in der Werkzeugkiste, könnten wir dann mal darangehen, eine RS232-Verbindung zwischen STK500 und PC einzurichten ;-). Was hältst Du davon?
Ciao,
mare_crisium
robo_wolf
04.05.2010, 17:10
mare_crisium,
OK die Erlaeuterung mit dem hibbligen Bediener leuchtet mir ein.
An den FIFO moechte ich mich gern versuchen.
Eventuell kommt aber noch die eine oder andere Frage. :-)
mare_crisium
05.05.2010, 17:32
robo_wolf,
nur zu mit Fragen :-) !
Für die nächste Aufgabe, die FIFO, habe ich etwas zusammengeschrieben, das - wie ich hoffe - Dir den Einstieg erleichtert ;-) .
Ciao,
mare_crisium
robo_wolf
05.05.2010, 17:43
mare_crisium,
wo du gerade schreibst.
Hab da gleich ne Frage:
Den FIFO stell ich mir so vor, dass er je nach Anfrage geleert und befuellt werden kann, bis er eben voll oder leer ist.(kein Schieberegister)
Wenn ich keinen Lesezugriff mache, wird er sich bis zum max fuellen.
Aber es kann auch sein, es sind z.B. 10 Byte im FIFO und ich lese 2b raus, fuelle dann wieder 5b rein und lese 1b raus... usw. Oder..?
Wenn es so werden soll, dann ist es wirklich nicht so easy... :-)
mare_crisium
05.05.2010, 20:27
robo_wolf,
ja, ja die Lese- und Schreibzugriffe erfolgen völlig unsynchronisiert. D.h. während ein Prozess Daten in die FIFO einschreibt kann ein anderer schon dabei sein, die Daten der Botschaft schon wieder auszulesen :-).
Das ist wie beim Fliessband an der Supermarktkasse: Die FIFO ist das Fliessband, der Kunde legt die Daten darauf und an der Kasse werden sie heruntergenommen. Je nachdem wer von den beiden schneller ist, kommt's zum Stau und Ärger beim Kunden oder zu gelangweilten Blicken von der Kasse ;-). Wichtig ist aber, dass jeder der beiden mit der ihm eigenen Arbeitsgeschwindigkeit werkeln kann, ohne dass der Gesamtprozess abstürzt. Genau das braucht man, wenn's z.B. darum geht, Geräte miteinander zu verbinden, die mit unterschiedlicher Geschwindigkeit Daten erzeugen bzw. verarbeiten.
Der Algorithmus, den ich in dem .pdf beschreibe, erledigt auch die Situtation, die Dir 'nicht so easy' vorkommt, mit Leichtigkeit (und ein paar "cli"s ;-) ), obwohl er so (verhältnismässig) einfach ist.
Ciao,
mare_crisium
robo_wolf
09.05.2010, 00:16
Guten Abend mare_crisium,
lang hat es gedauert... aber nun habe ich einen Stand des FIFO-Moduls erreicht, den ich Dir vorstellen moechte.
Ich hoffe die Kommentare sind ausreichend und verstaendlich geschrieben.
Auf Deine Kommentare bin ich schon sehr gespannt 8-[
mare_crisium
09.05.2010, 22:43
robo_wolf,
na, also 0h16' ist schon ziemlich früher Abend ;-) ! Hab' mir Deinen Entwurf heruntergeladen und werde mich demnächst melden.
Ciao
mare_crisium
mare_crisium
10.05.2010, 12:50
robo_wolf,
chapeau! Das ist Dir gut gelungen :-) . Meine Kommentare sind alle im Quelltext des Moduls. Weil ich im Hauptprogramm nichts geändert habe, hänge ich es hier nicht an.
Wie Du sehen wirst, habe ich zwei Prozeduren vergessen, die man beim Verwenden von FIFOs häufig braucht. Die müsstest Du noch nachrüsten; ist aber einfach. Danach reden wir 'mal über das Protokoll, das wir für die Unterhaltung zwischen den RS232s verwenden wollen.
Hast Du auf der PC-Seite ein Programm, das die RS232 bedienen kann? Ich habe mir eins mit Delphi geschrieben, das die "Serial"-Komponente aus dem (sehr empfehlenswerten) Heft "Toolbox" verwendet.
Ciao,
mare_crisium
robo_wolf
10.05.2010, 19:19
mare_crisium,
vielen Dank fuer die Blumen, Deine Kommentare und Verbesserungsvorschlaege.
Werde die Vorschlaege und Erweiterungen(2 Prozeduren) in meiner optimierten Variante demnaechst wieder vorstellen.
(ich hoffe, dass ich alles richtig verstanden habe)
"Hast Du auf der PC-Seite ein Programm, das die RS232 bedienen kann?"
Nein.... nur WindowsOnBoardmittel Hyperterminal
"Toolbox" kannte ich bis heute noch nicht... Danke fuer den Hinweis.
robo_wolf
11.05.2010, 22:21
Guten Abend mare_crisium,
leider steh ich bei Deinem ersten Kommentar wieder ein wenig auf dem Schlauch:
"Alle Schweierigkeiten verschwinden, wenn die Kapazität als
Eingangsparameter übergeben werden."
Ist das aehnlich wie bei den macro.... xxx,@0..gemeint???
Ist mir irgendwie unklar :-k
mare_crisium
12.05.2010, 19:00
Guten Feiertags-Vorabend, robo_wolf ;-) !
Deine Frage beantwortet sich am Besten anhand des Quellprogramms. Ich habe Dein Modul so umgebaut, dass es so läuft, wie ich mir das vorgestellt habe. War'n noch ein paar fette Fehler drin ;-) . Die in FIFO8_CREATE und FIFO8_WRITE habe ich beseitigt; FIFO8_READ überlasse ich Dir :-) !
Ciao,
mare_crisium
robo_wolf
13.05.2010, 09:39
wuensche einen schoenen Feiertag,
mare_crisium,
Danke fuer die Files.
Da hatte ich wohl etwas zu kompliziert gedacht(Eingangsparameter uebergeben).
Wenn man das so sieht, ist es doch logisch un simple zugleich... ;-)
Ein, fuer mich gravierendes, Problem sehe ich jetzt noch:
Daduch das die RAM-Konstrukte auf Variablen basieren, kann folgendes passieren.
Ich stelle die beiden FIFOs auf max:=255 und der "Controller wirft Blasen"
Was ich damit sagen will: Das RAM-Ende wird ueberschritten und der Assembler merkt es nicht... zu mindestens bei mir.
RAM_NEXT_FREE :=0x027E und beim AtMega8515 ist bei 0x025F schon Schluss.
Nun um dem Ganzen im Programm vorzubeugen, moechte ich gern eine Uberwachung einbauen.
Also RAM_NEXT_FREE mit RAMEND( ist ja so in der *.inc festgelegt) vergleichen.
Da sehe 2 Probleme zum Einen muss man vom RAMEND noch die Stackverwaltung abziehen...
und zum Anderen moechte ich bei der Abfrage einen Assembler-Error bewirken.
mare_crisium
13.05.2010, 14:59
robo_wolf,
hatte ich mir's doch gedacht, dass Du Dir das mit der Parameterübergabe irgendwie zu kompliziert vorgestellt hattest ;-) !
Ja, die Überprüfung des RAM-Haushalts ist nützlich. Allerdings sind die RAM-Bereiche in diesem Beispiel ja nicht dynamisch; will sagen: So wie sie zu Assemblierzeit eingestellt sind, so bleiben sie auch während der Laufzeit des Programms. Ich habe mich deshalb immer damit beholfen, nach dem Assemblieren einmal den Simulator laufen zu lassen und dort die Ausdehnung der RAM-Bereiche zu überprüfen (inkl. System-Stack).
Man könnte an noch ander Methoden denken, z.B. daran, zwischen Variablen-Bereich und Stack-Untergrenze sogenannte "Wächterbytes" einzubauen. Das sind zwei, drei RAM-Bytes mit vordefinierten Werten. Wenn die sich ändern, bedeutet es, dass der Stack in den Variablenbereich hineingewachsen ist. Diese Überwachung könnte in der IDLE-Schleife angesiedelt werden.
Ciao,
mare_crisium
robo_wolf
14.05.2010, 09:30
mare_crisium,
habe noch einen Fehler in meinem Programm gefunden, den ich nicht nachvollziehen kann.
Wenn ich die Kapazitaet groesser 63(0x3F) setze, bekomme ich die Fehlermeldung
..\LernPrgrm_FIFO_TEST_V01\FIFO8.asm(122): error: Operand 2 out of range: 0x40
--> es gibt ne Grenze...und die liegt bei "<0x40" .... Oder???
das habe ich noch gefunden:
FIFO8_WRITE:
/* KOMMENTAR_BEGINN!!!
...
cp r18,r17 ;
...
hier stimmt was noch nicht. Das zweite Byte wird nicht mehr geschrieben.
->>>(...wird schon geschrieben, ist aber beim ersten Mal "0x00")
KOMMENTAR_ENDE !!!
*/
cp r17,r18 ; die Operanten muessen vertauscht werden, weil es brge und nicht breq ist( :-() mein Fehler)
brge FIFO8_WR_EXIT ; Sprung, wenn FIFO voll ist
mare_crisium
14.05.2010, 13:32
robo_wolf,
ich vermute stark, dass Du es mit einer der AVR-Gemeinheiten zu tun hast, vor der ich Dich schon lange hätte warnen müssen:
adiw xh:xl,KONSTANTE ; mit KONSTANTE >= 64
geht nicht. Diese Anweisung kann Konstanten nur bis 63 verarbeiten. Um ENDADR auszurechnen musst Du eine reguläre 2-Byte-Addition benutzen ;-).
Ciao,
mare_crisium
robo_wolf
14.05.2010, 14:39
mare_crisium,
muss mich korrigieren...
habe nun bei der Prozedur FIFO8_WRITE
FIFO8_WRITE:
....
;
; brge -> -64 = k = +63
mov yl,r17
mov yh,r18
;
sub yl,yh
clr yh ;
andi yl,0x3F ; ist nie groesser 63
; brge -> -64 = k = +63
cp yh,yl
brge FIFO8_WR_EXIT ; Sprung, wenn FIFO voll ist
....
diese Zeilen eingefuegt.
Es gefaellt mir aber noch nicht so richtig.
mare_crisium
14.05.2010, 19:44
robo_wolf,
ich fürchte, in Deinem zweiten Posting hast Du Dich ein bisschen vergaloppiert ;-) . Dein erstes Posting (Heute, 14.05. um 09:30) bezieht sich auf Zeile 122 Deines Moduls "FIFO8.asm"
...
FIFO8_CREATE:
...
; FIFOkapazität speichern
std z+FIFO8_CTRL_CAP,r16 ; Kapazität speichern
; WRADR und RDADR auf Anfang setzen; ANFADR und ENDADR in RAM schreiben
mov xl,zl
mov xh,zh
adiw xh:xl,FIFO8_CTRL_LNG ; FIFO-Steuerung ueberspringen - auf erstes FIFO-Byte zeigen
std z+FIFO8_CTRL_WRADR_LOW,xl
std z+FIFO8_CTRL_WRADR_HIGH,xh
std z+FIFO8_CTRL_RDADR_LOW,xl
std z+FIFO8_CTRL_RDADR_HIGH,xh
;
std z+FIFO8_CTRL_ANFADR_LOW,xl
std z+FIFO8_CTRL_ANFADR_HIGH,xh
; !!! ACHTUNG:
adiw xh:xl,FIFO8_CAP ; ENDADR berechnen
; Über diese Zeile beschwert sich der Assembler, wenn FIFO8_CAP
; grösser als 64 ist. Deshalb musst Du sie durch
ldi r16,FIFO8_CAP
add xl,r16
clr r16
adc xh,r16
; ersetzten. Dann klappt's auch mit Kapazitäten > 64.
...
Das Problem Deines zweiten Postings verstehe ich nicht. Natürlich darf FIFO_CAP grösser als 64 sein! Du musst nur zum Berechnen von ENDADR eine anderen Weg gehen (siehe oben) ;-) !
Ciao,
mare_crisium
robo_wolf
14.05.2010, 20:07
Guten Abend mare_crisium,
...der 2.Teil bezieht sich auch auf das Posting von 09:30
>>>>
brge FIFO8_WR_EXIT ; Sprung, wenn FIFO voll ist
<<<<
Auch hier trifft das Problem mit dem k > 64 zu.
In der Version V02
; prüfen, ob FIFO8 voll ist
ldd r17,z+FIFO8_CTRL_CAP ; FIFOzähler mit FIFOkapazität vergleichen
ldd r18,z+FIFO8_CTRL_ZHLR ;
/* KOMMENTAR_BEGINN!!!
...
cp r18,r17 ;
...
hier stimmt was noch nicht. Das zweite Byte wird nicht mehr geschrieben.
KOMMENTAR_ENDE !!!
*/
cp r18,r17 ;
brge FIFO8_WR_EXIT ; Sprung, wenn FIFO voll ist
wenn FIFOkapazität(r17) >64 ist reagiert das Programm anders als wenn <64 ist.
< 64 tut es wie gewuenscht und
> 64 springt er gleich zu FIFO8_WR_EXIT:
mare_crisium
15.05.2010, 16:26
robo_wolf,
ich habe versucht, das Problem nachzuvollziehen, krieg's aber nicht hin :-( . Sowohl in Deiner ersten Version (mit den notwendigen Korrekturen) als auch in der Version _V02 schreibt er klaglos in FIFOs mit Kapazität >64, bis sie voll sind. Ich nehme an, dass Du inzwischen in Deinem Programm kräftig umgebaut hast - musst den aktuellen Stand 'mal posten!
Ciao,
mare_crisium
robo_wolf
15.05.2010, 17:41
mare_crisium,
bin ratlos...
FIFO8_V02.asm ist unveraendert die Version von Dir.
Bei LernPrgrm_FIFO_TEST_V02.asm habe ich doch nur die
.equ FIFO_FROM_SERIAL_CAP = 211 anstatt die 23 geschrieben.
Die anderen Aenderungen sind in einer V03 gemacht... :-k
Hier noch der Codeabschnitt....beim Aufruf von FIFO8_WRITE.
Ist FIFO_FROM_SERIAL_CAP <64 fuellt sich der RAM, bei >64 springt er zum Ende der Prozedur (FIFO8_WR_EXIT:).
; Hauptprogramm
;
; Jetzt füllen wir mal FIFO_FROM_SERIAL mit 1,2,3,4 ... bis sie voll ist
clr r16
ldi zl,low(FIFO_FROM_SERIAL)
ldi zh,high(FIFO_FROM_SERIAL)
RS_00:
inc r16
rcall FIFO8_WRITE
brts RS_00 ; wenn Schreiben erfolgreich, weiter schreiben
; so jetzt ist sie voll
mare_crisium
16.05.2010, 14:01
robo_wolf,
der Grund für das Problem besteht darin, dass der ATmega beim Vergleichen von "signed integer"-Werten ausgeht. Wenn Bit7 von FIFO_CAP gesetzt ist, stellt das für ihn eine negative Zahl dar. Wenn jetzt der Zähler auch 0x00 ist - die Kapazität ist auf alle Fälle kleiner. Deshalb führt er das Programm auf direktem Weg zum EXIT.
Die Lösung des Problems habe ich in _V03 eingebaut (nicht ohne Kommentar ;-) ). Ich hoffe, das Wochenende ist gerettet!
Ciao,
mare_crisium
robo_wolf
16.05.2010, 18:08
mare_crisium,
jaaa :-) ... ist gerettet. :-)
Was mich ein wenig gewundert hat, das es bei Dir funktioniert hat....(eventuell ein Wert zwischen 63 und 127 gewaehlt?)
Ich habe mir schon eine Loesung mit der Abfrage auf das ZERO-Flag ausgedacht.
Das wird ja bei Gleichheit der Werte von r17 und r18 gesetzt.
Aber die CARRY-Flag-Loesung ist "runder". (ganz ohne Zweifel)
Was ich Dich, vor Deinem heutigen Posting, noch fragen wollte:
Was passiert eigentlich bei einer Compare-Anweisung.
Wie findet der Vergleich statt? Substraktion der Werte mit Beruecksichtung des Vorzeichens(Bit7)?
Naja... mit der Info wollte ich dann auf Fehler- bzw. Loesungssuche.
PS. bevor ich es vergesse:
Was haelst Du davon fuer die RS232 Kommunication STK500 - PC, einen neuen Thread zu eroeffnen.
mare_crisium
16.05.2010, 21:56
robo_wolf,
freut mich :-) für Dein Wochenende!
Ich hatte den Fehler nicht bemerkt, weil ich nicht mit Kapazität > 127 experimentiert hatte. An dem Ganzen kann man gut sehen, wie leicht einem sowas durch die Lappen gehen kann und wie schwer es ist, gründlich zu testen ;-) !
Die Anweisung "cp Rd,Rr" führt tatsächlich eine Subtraktion aus. Die Registerinhalte werden als Werte in 2er-Komplement-Darstellung behandelt - dadurch bekommt Bit7 seine Bedeutung als Vorzeichen.
Das mit dem neuen Thread ist eine gute Idee; vielleicht gibt's dafür ja noch mehrere Interessenten. Aber lass' uns die FIFO noch hier fertigmachen.
Ciao,
mare_crisium
robo_wolf
17.05.2010, 22:08
Guten Abend mare_crisium,
- weiter am FIFO -
habe in den Code nun noch die beiden Prozeduren FIFO8_FLUSH und FIFO8_GETCOUNT eingefuegt.
Leider weis ich nicht genau wo und wann sie aufzurufen sind.
FIFO8_GETCOUNT kann bei jedem MAIN-Durchlauf aufgerufen werden...
FIFO8_FLUSH setzt ein Fehlverhalten voraus. Dieses muss erst erkannt werden, bevor der FIFO resetet wird.
Kannst Du mir da noch ein paar Infos geben?
oberallgeier
17.05.2010, 22:48
... vielleicht gibt's dafür ja noch mehrere Interessenten ...Aber sicher gibt es die. Meine Assemblererfahrung auf Atmels ist zwar nicht sehr gross, tausend Zeilen vielleicht oder zwei, aber es war MEIN Einstieg in die mikroelektronische Wunderwelt. Deshalb lese ich natürlich immer wieder mit einem Auge mit.
robo_wolf
19.05.2010, 06:59
Guten Morgen mare_crisium,
habe mir ein paar Gedanken zum FIFO8_FLUSH gemacht:
Gruende fuer FIFO8_FLUSH - Aufrufe:
1.
FIFO laeuft ueber
>>> Abfrage in FIFO8_GETCOUNT -- Vergleich FIFO8_CTRL_CAP mit FIFO8_CTRL_ZHLR -> wenn FIFO8_CTRL_ZHLR > FIFO8_CTRL_CAP kommt FIFO8_FLUSH
>>> im schlimmsten Fall wuerden aber andere Programmteile zerstoert !? - neues Problem entsteht
2.
????
Folgen fuer FIFO8_FLUSH - Aufrufe:
gepeicherter Inhalt(vor Fehler) ist nicht mehr verfuegbar
Sollten FIFO-Fehler signalisiert werden?
mare_crisium
19.05.2010, 22:26
Guten Abend robo_wolf,
ich habe Dein überarbeitetes Modul durchgesehen - die Kommentare, wie üblich, in der .asm-Datei im Anhang. Viel ist mir nicht mehr dazu eingefallen ;-) .
Die GETCOUNT und FLUSH Prozeduren braucht man erst im Zusammenhang mit der konkreten Anwendung: Bei der RS232-Anwendung, die wir planen, kommt es z.B. vor, dass eine Botschaft fehlerhaft empfangen wurde. Dann hat's keinen Sinn, viel Zeit damit zu vertun, die Botschaft Byte für Byte aus der FIFO auszulesen, nur um sie zu löschen. Mit der FLUSH-Prozedur geht das viel effektiver. Besonders bei langen FIFOs.
GETCOUNT braucht man in dieser Anwendung oft, um zu prüfen, ob die Botschaft auf dem PC richtig zusammengestellt wurde. Z.B. kommt es oft vor, dass ein bestimmter Befehl vom PC an den ATmega eine bestimmte Anzahl Bytes als Parameter braucht. Da ist es praktisch, gleich mal nachzugucken, ob genug Parameter da sind, bevor man anfängt, sich durch die Dekodierung des Befehls zu quälen.
So, wie Du siehst, geht's schon los mit der RS-232-Anwendung :-) !
Ciao,
mare_crisium
robo_wolf
21.05.2010, 08:59
mare_crisium,
zum Thema FIFO8_CTRL_ANFADR_* hast Du natuerlich Recht.
Fuer den Anfang war es fuer mich uebersichtlicher diese Groesse als Variable zu sehen und damit arbeiten zu koennen.
Der logische Schritt ist aber nun den Code schlanker und schneller zu machen.
...wollte eigentlich auch auf ENDADR verzichten, da ist der Code(zumindestens bei mir) deutlich mehr
Hier nun die geaenderte Version.
mare_crisium
01.06.2010, 15:12
robo_wolf,
so, jetzt geht's weiter. Ich habe mir Deine Version V05 vorgenommen und noch etwas verbessert (wie ich hoffe). Die Schreibprozedur kostet jetzt max. 70 und die Leseprozedur 68 Zyklen. D.h. beide bleiben bei 16MHz unter 5 Mikrosekunden. Damit sollten sie für die RS232-Anwendung fit genug sein.
Wenn Du für die RS232 einen neuen Thread aufmachen willst: Nur zu :-) !
Ciao,
mare_crisium
robo_wolf
02.06.2010, 16:30
mare_crisium,
Danke fuer Deine Kommentare.
Da waren ja doch noch einige Schnitzer drinnnen.
Das man den Code beschleunigen konnte war mir klar, nicht aber die Fehler... schaehm. ](*,)
Wie ich Dir schon geschrieben habe, moechte ich beim RS232 einige Daten und Gedanken zusammentragen
und mit diesen werde ich dann den Thread eroeffnen.
...hoffe noch bis zum Wochenende.
Powered by vBulletin® Version 4.2.5 Copyright ©2024 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.