Interrupts beim Tiny2313 (Assembler)

Rolf.H

Neues Mitglied
17. Juni 2012
136
0
0
89
25451 Quickborn
Sprachen
Hallo Leute,
hab mich mal mit "extern Interrupt" beschäftigt. Dazu das Buch zur Hilfe genommen, was eine gut
verständliche Applikation zum Mega8 hatte. Also habe ich versucht dies auf den Tiny2313 zuzuschneiden.
Vieles fiel zu Beginn weg wie SPL/SPH...die include geändert. Habe mich zum Pin "INT1" entschieden, da eine
Taste angelegt (mit Pullup = 10K) und an PB0-PB2 3 Leds.
Neu war für mich INT1 zu initialisieren. In der Liste SFR fand ich das Reg. MCUCR, aber nicht GICR. Es kam nur
GIMSK in Frage und das stimmte auch. Die ganze Sache lief auf Anhieb, was mich aber dabei wunderte, vom Tasten-
prellen war nichts zu merken. Nach jedem Tastendruck leuchteten die Leds binär sauber auf. Zu Beginn wurde
Port DDRD nicht als Eingang deklar. Sind die Ports nach Reset automatisch als Input geschaltet?
Die Anwendung kann ich evtl. für die Readkontakte auf den Schienen verwenden.
Nun hat ja PORTB die 8 PCINTx Pins...wie mag das da nur ablaufen. Brauchte da einen Tipp!

Grüß

Rolf
Hier der Quelltext:

Code:
; Projekt-Name: Projekt01                    Datum: 12.12.2012
; Datei: ext-interrupt01.asm
; INT1 Taste PD3 / Pin7
; Port B: Ausgabe PB7 .. PB0 3 x  Leuchtdioden  
; Port D: Eingabe PD3=INT1 / Pin7 über Taste sw1
; Konfiguration: interner Oszillator 1 MHz, externes RESET-Signal 
        .INCLUDE  "tn2313def.inc" ; Deklarationen für Tiny2313
        
        .DEF    akku = r16      ; Arbeitsregister
        .CSEG                   ; Programm-Flash
        rjmp    start           ; Reset-Einsprung
        .ORG    INT1addr        ; Einsprung externer Interrupt INT1
        rjmp    TIMER0_OVF      ; Sprung zur ISR
        .ORG    $2A             ; weitere Interrupteinsprünge übergehen
start:  
        ldi     akku,0xFF        ; Bitmuster 1111 1111
        out     DDRB,akku       ; Richtung Port B ist Ausgang
        clr     akku            ; Zähler r16 löschen
        out     PORTB,akku      ; und Anfangswert ausgeben(PORTB=LOW) 
; Interrupt INT1 initialisieren
        in      akku,MCUCR      ; Inhalt Steuerreg. nach Reset=0
        sbr     akku,1 << ISC11 ; setze  Bit ISC11
        cbr     akku,1 << ISC10 ; lösche Bit ISC10
        out     MCUCR,akku      ; ISC1x: 1 0 INT1 fallende Flanke
        in      akku,GIMSK      ; Freigaberegister nach Reset=0
        sbr     akku,1 << INT1  ; setze Bit INT1: 
        out     GIMSK,akku      ; Interrupt INT1 freigegeben
        sei                     ; alle Interrupts global frei
; Hauptprogramm schläft vor sich hin
loop:   rjmp    loop            ; tu nix
;
; Serviceprogramm bedient externen Interrupt INT1 Taste PD3

TIMER0_OVF:
        push    akku             ; Register retten auf Stack -1
        in      akku,SREG        ; Statusreg. nach r16 
        push    akku             ; retten auf Stack -1
        in      akku,PORTB       ; PORTB=0 nach r16
        inc     akku             ; um 1 erhöhen
        out     PORTB,akku       ; neuen Zählerstand ausgeben
        pop     akku             ; SP+1, danach vom Stack in r16 laden
        out     SREG,akku        ; Inhalt von r16 ins SREG laden
        pop     akku             ; SP+1, danach vom Stack in r16 laden
        reti                     ; Rücksprung aus Serviceprogramm
        .EXIT                    ; Ende des Quelltextes
 
Hallo Rolf
tu dir und uns doch einen Gefallen und öffne mit neuen Problemen auch einen neuen Thread. Dann werden das nicht so ellen lange Beiträge. Nun zur Sache:
Das Prellen wirst du auch nicht meken, wenn du eine Taste drückst und auf ein Signal einer LED achtest. Aber, setz mal einen Zählvorgang drauf......
Eingänge mit Interrupt abfragen ist nicht unbedingt sinnvoll. Klar, wenn du nur einen kurzen Impuls hast und du bist dir nicht sicher, das die Zykluszeit deines Programmes diesen Impuls überschreitet, dann macht ein Interrupt Sinn. Ansonsten ist das "EVA"-Prinzip völlig ausreichend. Nun zur Art deiner ISR...
Code:
.DEF    akku = r16      ; Arbeitsregister
        .CSEG                   ; Programm-Flash
        rjmp    start           ; Reset-Einsprung
        .ORG    INT1addr        ; Einsprung externer Interrupt INT1
        rjmp    TIMER0_OVF      ; Sprung zur ISR
        .ORG    $2A             ; weitere Interrupteinsprünge übergehen

Sauber ist das nicht. Du weißt hoffentlich, was eine "Interrupt Vector Table" ist. Wenn du deine Interrupt Service Routine für Eingänge Mit "RJMP Timer_OVF" aufrufst, das funktioniert natürlich. Aber das ist nicht die Timer-ISR sondern eine Int1Addr-ISR. So kommst du ganz schnell ins Schwimmen, wenn du wirklich mal eine Timer und eine INT1 ISR brauchst. Schau dir mal die ganze IVT an. Ich hab mir angewöhnt, grundsätzlich alle Interrupt -Einsprungsadressen in mein Programm zu kopieren. Interrupts, die nicht gebraucht werden, bekommen einfach ein "RETI" und gut. Das schont nebenbei auch die Nerven, denn Interrupts haben die Eigenschaft zu kommen wann sie wollen und wenn du in die IVT aufgrund eines nicht erfassten Interrupts hinein springst, fehlt das "RETI" evtl. oder eine ganz andere ISR wird aktiviert. Da kann man dann nur zur erfolgreichen Fehlersuche gratulieren........
Hier noch einmal kurz erklärt, was Interrupts tun, wenn sie ausgelöst werden:
Der aktuelle Programmcounter wird auf den Stack gepackt und es wird die Aresse des zugehörigen Interrupts in den Programmcounter geladen. Der Nächste Befehl wird also von dieser "festverdrahteten" Adresse geholt. Steht dort nichts...... ist das wie ein NOP, nimmt er die nächste Adresse und schaut nach, ob da einn Befehl ist. So läuft er vielleicht wieder ins Programm, natürlich ohne die auf dem Stack liegende Rücksprungadresse zurückzuholen und den Stack zu bereinigen. Kommt der Interrupt im Sekundenbereich, kannst du dir selber ausrechnen, wann deine Variablen überschrieben werden. Irgendwann landet dann auch der Stack wieder bei seiner Ursprungsadresse, weil ja nichts diesen Stacküberlauf detektiert. Schön, du merkst noch nicht einmal, das dein Programm blödsinn macht. Nur wenn du Variablen benutzt, haben die dann sicherlich irgendetwas enthalten, nur nicht das, was drin stehen sollte.
Also, wenn du eine ISR aufrufen willst, dann gib ihr auch den zugehörigen Namen. Und gewöhn dir an, die komplette IVT zu laden. grad als Anfänger ist das wichtig. Oder bist du dir 100 % sicher, das "nur" der von dir bearbeitete Interrupt programmiert unf freigegeben ist ?
Gruß oldmax
 
"Steht dort nichts" gibts nicht. Die Bits des Word haben immer(!) irgendeinen logischen Zustand. Entweder 0 oder 1. Werden nicht eher alle nicht anders zu beschreibenden Flash-Adressen automatisch zu 0x0000 (was der Opcode von NOP IST)?

Das mit dem IVT komplett (mit Retis) zu beschreiben ist auch ein Glaubenskrieg.
Ein Interrupt wird nicht aus versehen ausgelöst, sondern NUR dann, wenn man das entsprechende Interrupt-Enable-Bit gesetzt hat (und dann die Quelle triggert), man also absichtlich vorhat, den Interrupt zu verwenden.
In diesem Fall muß natürlich die weitere Vorgehensweise in der IVT geklärt sein (was nicht immer ein Sprung sein muß).
Sich durch "Reti"s in der IVT vor unbehandelten Interrupts abzusichern ist die Behandlung von Symptomen an Stelle von Ursachen.

Zur Wahl der Labelnamen: natürlich kann man da (nahezu) jeden Quatsch als Name verwenden - nur macht es zum Verständnis des Codes natürlich am meisten Sinn, wenn sich Die Aufgabe des folgenden Blockes, die Funktion daraus erahnen läßt. Dasselbe gilt auch beim benamsen Registern und bei Variablen.

Außerdem greifst Du bei den verwendeten Interruptvektoren auf die Definition in der Definitionsdatei zu (ok), dein eigentliches Programm läßt Du bei der Flash-Adresse 0x002A weitermachen. Warum eigentlich da? der letzte Interruptvektor des Tiny2313 ist 0x0012, dein Programm könnte also (selbst mit oldmax's RETIs bei nicht verwendeten IVTs) bei 0x0013 weitergehen. Konsequenterweise könnte man dann auch gleich für .org die (in der Definitionsdatei definierte) Variable INT_VECTORS_SIZE verwenden.
 
Bei INT0 und INT1 (also den "alten" externen Ints) handelt es sich um 2 verschiedene Interrupts - jedes Beinchen hat sein eigenes interrupt Flag, seinen eigenen eigenen Interrupt Vektor. Außerdem kannst Du konfigurieren, was genau den Interrupt auslösen soll (low Pegel, Pegelwechsel, fallende Flanke, steigende Flanke). Aufgrund der getrennten Interruptvektoren kannst Du also den beiden Pins auch 2 verschiedene ISR verpassen (es gibt auch Chips mit mehr als 2 "echten" externen Ints).

Bei den PinChangeInterrupts hast Du diesbezüglich weniger Möglichkeiten - jeder Pegelwechsel löst ihn aus. dafür werden aber mehrere Pins (welche das sind, ist von der Hardware vorgegeben) zusammengefaßt - Du kannst lediglich festlegen (maskieren, mit dem PCMSK), welche Pins dieser Gruppe den gemeinsamen Interrupt triggern dürfen. Danach wird diese ISR ausgeführt - Du selbst mußt Dich jetzt eventuell darum kümmern herauszubekommen, welche Flanke an welchem Pin den ausgelöst hat. (Dein Tiny hat nur einen PCI für die Beinchen B0 bis B7, bei anderen Chips mit mehr Beinchen gibt es dann mehrere getrennte PCIs, klar).

Bei einigen Chips kannst Du so sowohl den INT0 als auch den entsprechenden PCI (ein und dasselbe Bein) aktivieren. Dann wird erst der INT0 abgearbeitet, danach der PCI - den Sinn mal dahingestellt.

Um bei Deinen Reeds zu bleiben - Du könntest so einen IRQ auslösen, wenn sich an irgendeinem der 8 Reeds was getan hat - Du müßtest dann (in der ISR) das PINB-Register auswerten, um zu sehen wo und was. Dabei fällt sowohl beim schließen, als auch beim öffnen ein IRQ. Hinzu kommt noch das kontaktprellen...
Wenns allerdings mehr als 8 Reeds und so werden sollen, werf ich mal den Begriff Portexpander ein (Das sind Chips, die sich über SPI/TWI seriell ansteuern lassen, nach außen halt sowas wie einen 8bit I/O haben, und in der Lage sind, einen interrupt am Controller auszulösen - Cassio kann Dir (meiner Meinung nach) dazu Erfahrungsberichte liefern
 
Hi
Es ist zwar noch früh, aber trotzdem will ich nochmal ein paar Worte zur IVT sagen....
Das mit dem IVT komplett (mit Retis) zu beschreiben ist auch ein Glaubenskrieg.
Nun ja... will ich so mal nicht stehen lassen, denn so wie Rolf mit einem Interrupt umgeht, denk ich, hilft eine vollständige IVT dem Verständnis und, es kostet nichts, diese immer vollständig einzutragen. Ist ja nur ein C&P. Der dadurch belegte Speicher ist für Programm eh nicht nutzbar. Für Rolf hier mal die IVT eines AtMega8
Code:
.ORG INT_VECTORS_SIZE 

.org INT0addr 		;RJMP 	EXT_INT0	; External Interrupt0 Vector Address    
			RETI   
.org INT1addr		; External Interrupt1 Vector Address       
			RETI                   
.org OC2addr		; Output Compare2 Interrupt Vector Address       
			RETI                   
.org OVF2addr		; Overflow2 Interrupt Vector Address       
			RETI                   
.org ICP1addr		; Input Capture1 Interrupt Vector Address       
			RETI                   
.org OC1Aaddr		; Einsprungadresse ISR Timer0
			RJMP 	isrTimer1                
.org OC1Baddr		; Output Compare1B Interrupt Vector Address       
			RETI                   
.org OVF1addr		; Overflow1 Interrupt Vector Address       
			RETI                   
.org OVF0addr		; Overflow0 Interrupt Vector Address       
			RETI                   
.org SPIaddr		; SPI Interrupt Vector Address       
			RETI                   
.org URXCaddr   		; USART Receive Complete Interrupt Vector Address       
			RJMP	int_rxc                 
.org UDREaddr		; USART Data Register Empty Interrupt Vector Address       
			RETI                   
.org UTXCaddr		; USART Transmit Complete Interrupt Vector Address       
			RETI            
.org ADCCaddr	; ADC Interrupt Vector Address       
			RETI                   
.org ERDYaddr		; EEPROM Interrupt Vector Address       
			RETI                   
.org ACIaddr		; Analog Comparator Interrupt Vector Address       
			RETI                   
.org TWIaddr		; Irq. vector address for Two-Wire Interface       
			RETI               
.org SPMRaddr		; SPM complete Interrupt Vector Address       
			RETI
Ich denke, grad ein Anfänger ist besser damit beraten, eine möglichst vollständige IVT in seinen ersten Projekten zu haben. Ob ein Profi das für erforderlich hält oder nicht, steht doch gar nicht zur Diskussion. Auch wird ein Profi nur Interrupts haben, die wie du schon sagst, auch haben will. Rolf, nimm es mir nicht übel, aber dir trau ich alles zu.... :D
Gruß oldmax
 
Ich denke, grad ein Anfänger ist besser damit beraten, eine möglichst vollständige IVT in seinen ersten Projekten zu haben. Ob ein Profi das für erforderlich hält oder nicht, steht doch gar nicht zur Diskussion. Auch wird ein Profi nur Interrupts haben, die wie du schon sagst, auch haben will. Rolf, nimm es mir nicht übel, aber dir trau ich alles zu.... :D
Gruß oldmax

aber oldmax...wenigstens einer, dem Du alles zutraust! Dann müßte ich dem Buch und dessen Verfasser
auch alles zutrauen, von dem ich das Ganze übernommen hatte.
Ich steig ja erst neu in die Materie ein "ext. Interrupt" und stelle mein Quelltext hier ein.
Glaube, ich bin hier fast der Einzige der sein Assembler-Wissen reinstellt.

LotadaC bleibt immer sachlich...das muß ich ihn hoch anrechnen!

Grüße

Rolf
 
...es kostet nichts, diese [IVT] immer vollständig einzutragen. Ist ja nur ein C&P. Der dadurch belegte Speicher ist für Programm eh nicht nutzbar...
Natürlich ist dieser Bereich des Flash für beliebigen Code nutzbar. Die Zellen können als Einsprungziel verwendet werden (indem man den Interrupt freigibt, und wenn dieser Eintritt), ansonsten ist dieser Bereich nichts besonderes.
Wenn man dort irgendwelchen Code platziert, wird der natürlich auch ausgeführt. Also wenn dort direkt eingesprungen wird, oder der der PC diese Zelle durch ein normales inkrement erreicht - klar.

Insbesondere kann man bei einem AVR mit 2-word-IVT ISRs, die nur 1-Word Code haben, diesen direkt in die IVT schreiben (der 1-Word-Code ist das erste Word, das Reti das zweite.)

Außerdem kann(!) man die ISR zu einen freigegebenen IRQ auch direkt am entsprechenden Einsprungpunkt in der IVT beginnen lassen. Man muß dabei eben nur im Kopf behalten, daß man dabei ggf andere IVT-Einsprungpunkte mit Code beschreibt, und somit (in den allermeisten Fällen) diese Interrupts nicht mehr sinnig verwendet werden können (dieweil der Code ja schon feststeht).

Schau Dir mal die Appnote 410 an, (IR-Reciever), da haben die das selbst so vorgeschlagen.

Assembler ist keine Hochsprache, die einem das Denken mit vorgefertigten Automatismen abnimmt. Hier kannst Du jeden Quatsch programmieren, mußt aber eben selbst mitdenken, was Dein Code bewirkt, bzw welcher Code Dein gestelltes Problem wie löst.
 
Hi
Ähhh, LotadaC, wolltest du mich jetzt belehren... ? Mag ja sein, das mein Text etwas mißverständlich war, aber ich weiß sehr wohl mit dem mir vorliegenden Assembler umzugehen. Das man, auch wenn ich's nicht für nötig halte, eigenen Code in die IVt bringen kann, hab ich mal für mich behalten. (Ausgenommen den erforderlichen Anweisungen für die ISR, das hab ich mal vorausgesetzt, das das klar war. ) Auch, das nicht beschriebene Speicherzellen einen Wert haben, also einen Inhalt, ist auch klar.

Trotzdem, auch ich bin nicht allwissend und freue mich über Tips.
Gruß oldmax
 
Bei INT0 und INT1 (also den "alten" externen Ints) handelt es sich um 2 verschiedene Interrupts - jedes Beinchen hat sein eigenes interrupt Flag, seinen eigenen eigenen Interrupt Vektor. Außerdem kannst Du konfigurieren, was genau den Interrupt auslösen soll (low Pegel, Pegelwechsel, fallende Flanke, steigende Flanke). Aufgrund der getrennten Interruptvektoren kannst Du also den beiden Pins auch 2 verschiedene ISR verpassen (es gibt auch Chips mit mehr als 2 "echten" externen Ints).

Bei den PinChangeInterrupts hast Du diesbezüglich weniger Möglichkeiten - jeder Pegelwechsel löst ihn aus. dafür werden aber mehrere Pins (welche das sind, ist von der Hardware vorgegeben) zusammengefaßt - Du kannst lediglich festlegen (maskieren, mit dem PCMSK), welche Pins dieser Gruppe den gemeinsamen Interrupt triggern dürfen. Danach wird diese ISR ausgeführt - Du selbst mußt Dich jetzt eventuell darum kümmern herauszubekommen, welche Flanke an welchem Pin den ausgelöst hat. (Dein Tiny hat nur einen PCI für die Beinchen B0 bis B7, bei anderen Chips mit mehr Beinchen gibt es dann mehrere getrennte PCIs, klar).

Bei einigen Chips kannst Du so sowohl den INT0 als auch den entsprechenden PCI (ein und dasselbe Bein) aktivieren. Dann wird erst der INT0 abgearbeitet, danach der PCI - den Sinn mal dahingestellt.

Um bei Deinen Reeds zu bleiben - Du könntest so einen IRQ auslösen, wenn sich an irgendeinem der 8 Reeds was getan hat - Du müßtest dann (in der ISR) das PINB-Register auswerten, um zu sehen wo und was. Dabei fällt sowohl beim schließen, als auch beim öffnen ein IRQ. Hinzu kommt noch das kontaktprellen...
Wenns allerdings mehr als 8 Reeds und so werden sollen, werf ich mal den Begriff Portexpander ein (Das sind Chips, die sich über SPI/TWI seriell ansteuern lassen, nach außen halt sowas wie einen 8bit I/O haben, und in der Lage sind, einen interrupt am Controller auszulösen - Cassio kann Dir (meiner Meinung nach) dazu Erfahrungsberichte liefern

das ist verständlich geschrieben....da komme ich noch drauf zurück!
IVT heißt das Interrupt-Vektor-Tabelle oder wie?
 
nun habe ich mich nochmal mit INT1 und INT0 beschäftigt, das Buch daneben.
Wie LotadaC schreibt:
1. jedes Beinchen hat sein eigenes Interrupt-Flag..was im Register GIMSK festgelegt ist. (Bit7=INT1, Bit6=INT0)
0=gesperrt, 1=frei (beim Tiny2313)

2. seinen eigenen Interrupt-Vektor...INT1=.ORG INT1addr auf Programmadresse 0x002, INT0=.ORG INT0addr auf 0x001

3. wann der Interrupt ausgelöst werden soll..das entscheide ich im Reg. MCUCR, dazu dienen die Wahrheitstabellen
von Bit0 bis Bit3

habe ich das so richtig erkannt?
Hier nochmal der etwas abgeänderte Quelltext streng an das Buch angelehnt.

Code:
 .DEF    akku = r16      ; Arbeitsregister
        .CSEG                   ; Programm-Flash
        rjmp    start           ; Reset-Einsprung
        .ORG    INT1addr        ; Einsprung externer Interrupt INT1
        rjmp    taste           ; Sprung zur ISR (so steht es im Buch)
        .ORG    $2A             ; weitere Interrupteinsprünge übergehen
start:  
        ldi     akku,0xFF       ; Bitmuster 1111.1111
        out     DDRB,akku       ; Richtung Port B ist Ausgang
        clr     akku            ; Zähler r16 löschen
        out     PORTB,akku      ; und Anfangswert ausgeben(PORTB=LOW) 
; Interrupt INT1 initialisieren
;der Anfangszustand der Bits von MCUCR u. GIMSK nach Reset=0        
        sbr     akku,(1<<ISC11) ; setze  Bit ISC11
        cbr     akku,(1<<ISC10) ; lösche Bit ISC10
        out     MCUCR,akku      ; ISC11=1/ISC10=0 >>INT1 fall. Flanke
        clr     akku            ; r16=0
        sbr     akku,(1<<INT1)  ; setze Bit INT1: 
        out     GIMSK,akku      ; Interrupt INT1 freigegeben
        sei                     ; alle Interrupts global frei
; Hauptprogramm schläft vor sich hin
loop:   rjmp    loop            ; tu nix
;
; Serviceprogramm bedient externen Interrupt INT1 Taste PD3

taste:
 
Sauber ist das nicht. Du weißt hoffentlich, was eine "Interrupt Vector Table" ist. Wenn du deine Interrupt Service Routine für Eingänge Mit "RJMP Timer_OVF" aufrufst, das funktioniert natürlich. Aber das ist nicht die Timer-ISR sondern eine Int1Addr-ISR. So kommst du ganz schnell ins Schwimmen, wenn du wirklich mal eine Timer und eine INT1 ISR brauchst. Schau dir mal die ganze IVT an. Ich hab mir angewöhnt, grundsätzlich alle Interrupt -Einsprungsadressen in mein Programm zu kopieren. Interrupts, die nicht gebraucht werden, bekommen einfach ein "RETI" und gut. Das schont nebenbei auch die Nerven, denn Interrupts haben die Eigenschaft zu kommen wann Hier noch einmal kurz erklärt, was Interrupts tun, wenn sie ausgelöst werden:
Der aktuelle Programmcounter wird auf den Stack gepackt und es wird die Aresse des zugehörigen Interrupts in den Programmcounter geladen. Der Nächste Befehl wird also von dieser "festverdrahteten" Adresse geholt. Steht dort nichts...... ist das wie ein NOP, nimmt er die nächste Adresse und schaut nach, ob da einn Befehl ist. So läuft er vielleicht wieder ins Programm, natürlich ohne die auf dem Stack liegende Rücksprungadresse zurückzuholen und den Stack zu bereinigen. Kommt der Interrupt im Sekundenbereich, kannst du dir selber ausrechnen, wann deine Variablen überschrieben werden. Irgendwann landet dann auch der Stack wieder bei seiner Ursprungsadresse, weil ja nichts diesen Stacküberlauf detektiert. Schön, du merkst noch nicht einmal, das dein Programm blödsinn macht. Nur wenn du Variablen benutzt, haben die dann sicherlich irgendetwas enthalten, nur nicht das, was drin stehen sollte.
Also, wenn du eine ISR aufrufen willst, dann gib ihr auch den zugehörigen Namen. Und gewöhn dir an, die komplette IVT zu laden. grad als Anfänger ist das wichtig. Oder bist du dir 100 % sicher, das "nur" der von dir bearbeitete Interrupt programmiert unf freigegeben ist ?
Gruß oldmax

Hallo oldmax,
ich versuche das zu verstehen!
Will ich eine ISR aufrufen soll ich ihr den zugehörigen Namen geben. Wäre der denn bei INT1 gleich INT1ADDR.

Würde das so aussehen?

INT1ADDR: push r16
in r16,SREG

usw.

reti
Und zu Beginn eingeben rjmp INT1ADDR

Warum giebt der Verfasser des Buchs einfach "taste" vor.
Irgendwie schwimme ich noch. Helfe mir mal!

Grüße

Rolf
 
Hallo Rolf,
Warum giebt der Verfasser des Buchs einfach "taste" vor.
Irgendwie schwimme ich noch. Helfe mir mal!

ich antworte mal kurz, ich hoffe es ist in Ordnung.

Es handelt sich bei "taste" lediglich um ein Label, also um eine Benennung eines Sprungziels. Dieses kannst du wählen, wie du möchtest, so wie bei "normalen" Routinen auch.
Bei den Einsprungadressen der Interruptservice-Routinen sind in den Definitionsfiles bereits Label definiert, diese kannst du verwenden, musst es aber nicht. Du kannst dir diese auch selber definieren oder gibst einfach entsprechend die Adresse direkt an, von der aus der Sprung in deine Interruptserviceroutine erfolgen soll.

Das kanns so aussehen:

Code:
.org INT1addr
rjmp MeineInterurptroutine1

; ...
MeineInterurptroutine1:
;...
reti

oder auch so:
Code:
.org 0x0002
rjmp MeineInterurptroutine1

; ...
MeineInterurptroutine1:
;...
reti

Für "MeineInterurptroutine1" wählst du am besten ein sinnvolles Label. Hier zum Beispiel "ExternerInterrupt1".

Ich weiß nicht, ob es hier schon gepostet wurde. Dies sind die Definitionen für die Label der Interruptvektoren beim ATtiny2313.

Dirk :ciao:

Code:
; ***** INTERRUPT VECTORS ************************************************
.equ    INT0addr    = 0x0001    ; External Interrupt Request 0
[COLOR=#b22222][B].equ    INT1addr    = 0x0002    ; External Interrupt Request 1[/B][/COLOR]
.equ    ICP1addr    = 0x0003    ; Timer/Counter1 Capture Event
.equ    OC1Aaddr    = 0x0004    ; Timer/Counter1 Compare Match A
.equ    OC1addr    = 0x0004    ; For compatibility
.equ    OVF1addr    = 0x0005    ; Timer/Counter1 Overflow
.equ    OVF0addr    = 0x0006    ; Timer/Counter0 Overflow
.equ    URXCaddr    = 0x0007    ; USART, Rx Complete
.equ    URXC0addr    = 0x0007    ; For compatibility
.equ    UDREaddr    = 0x0008    ; USART Data Register Empty
.equ    UDRE0addr    = 0x0008    ; For compatibility
.equ    UTXCaddr    = 0x0009    ; USART, Tx Complete
.equ    UTXC0addr    = 0x0009    ; For compatibility
.equ    ACIaddr    = 0x000a    ; Analog Comparator
.equ    PCIaddr    = 0x000b    ; 
.equ    OC1Baddr    = 0x000c    ; 
.equ    OC0Aaddr    = 0x000d    ; 
.equ    OC0Baddr    = 0x000e    ; 
.equ    USI_STARTaddr    = 0x000f    ; USI Start Condition
.equ    USI_OVFaddr    = 0x0010    ; USI Overflow
.equ    ERDYaddr    = 0x0011    ; 
.equ    WDTaddr    = 0x0012    ; Watchdog Timer Overflow
 
Kleine Ergänzung:
Der untere, von Dirk zitierte Code-Block ist ein Ausschnitt aus der Prozessordefinitionsdatei, die Du zu beginn inkludierst. Insbesondere werden hier Compiler-Variablen definiert (.equ), die Dir dann in Deinem Programm (Quelltext) zur Verfügung stehen. Überall, wo Du jetzt also "INT1addr" verwendest, ersetzt der Compiler das durch eine 2 (0x0002).
Die .org-Compilerdirektive bewirkt, daß die folgende Instruktion an der Stelle in den Flash geschrieben wird, die Du mit dem Argument hinter .org festlegst. (Kann man sich so vorstellen, daß .org den "Flash-Schreib-Zeiger" setzt).

RJMP - Relative Jump (relativer Sprung) führt, wie der Name sagt, einen relativen Sprung durch. Relativ zur aktuellen Position des Programm(Flashadress)zählers. Als Argument wird eine 12-Bit-Zahl erwartet (2erkomplement(?)). Damit Dir aber nicht bei der Codeentwicklung, und jeder -änderung vor lauter Rechnerei der Kopf qualmt, nimmt die Assembler-Entwicklungsumgebung Dir diese Rechnerei ab. Die Label (Text mit Doppelpunkt am Ende) erzeugen ja keinen Code - sie sind nur Markierungen für den Compiler.
Findet er also als Argument einer Instruktion nicht die erwartete Zahl, sondern einen "String", versucht er das entweder als Variable/Konstante zu interpretieren, oder als Adresse eines Labels. Bei den Labeln wird dabei (entsprechend der Instruktion) entweder die absolute Position, oder die Distanz übernommen - mußt Dich also nicht drum kümmern.
Kann er weder 'ne entsprechende Variable, noch 'nen entsprechenden label finden, gibts 'ne Fehlermeldung. Ebenso, wenn die Zahl (die durch die Variable, Labeladresse festgelegt ist) zu groß für die Instruktion ist (also man zB mit IN/OUT den I/O-Bereich jenseits der 0x3F erreichen will).(*)
Als Labelname kannst Du (wie auch bei den Variablen) nahezu jeden erdenklichen Quark hinschreiben - für die Entwicklungsumgebung müssen die nur eindeutig und auffindbar sein. Aber für Dich (und diejenigen, die Dir helfen sollen) solltest Du "klingende" Namen wählen, und natürlich keine irreführenden...

(*)Aber Vorsicht: verwendet man die, in der Definitionsdatei definierten Namen der I/O-Register, ist zu beachten, daß die ersten 0x3F I/O-Register für IN/OUT und LD/ST unterschiedliche Adressen haben. Bei LD/ST sind alle Adressen "um 32 hochgeschoben", nämlich genau um die 32 Rechenregister, die man mit LD/ST (auch) erreichen kann. In der Definitionsdatei sind die ersten 0x3F I/O-Registernamen mit den Adressen passend zu IN/OUT belegt, die höheren mit denen passend zu LD/ST. Die Variable SREG entspricht also zB 0x3F, mit "IN Rechenregister, SREG" wird also auch wirklich das SREG (0x3F) geladen. verwendet man aber "LD Rechenregister, SREG", wird nicht das SREG geladen, sondern das Register, welches 32 Adressen vorher steht (beim Mega88 EECR oder so).

Edit: lies Dir einfach mal die includierte Definitionsdatei durch, da wird einiges klarer (im wesentlichen werden da ja nur Variablen/Konstanten definiert und mit Werten beladen)
Und dann vergleiche das mal mit dem "Register Summary" des Controllerdatenblattes...;)
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)