PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Adressierung - Verständnisproblem



BlackDevil
03.11.2008, 19:39
Servus

Da wir in der Vorlesung jetzt mit Assembler begonnen haben und ich in den Ferien nicht weiter dazu gekommen bin mich mit dem Thema zu befassen muss ich doch wieder eine Grundlagen Frage stellen.

Interrupts wurden nicht behandelt, kamen aber im Beispielprogramm vor. Also zum AVR Tutorial und nachgesehen:



Aufbau der Interruptvektortabelle

Jetzt müssen wir dem Assembler nur noch klarmachen, dass er unser rjmp interrupt0 an die richtige Stelle im Programmspeicher schreibt, nämlich an den Interruptvektor für INT0. Dazu gibt es die Assemblerdirektive. Durch .org 0x001 sagt man dem Assembler, dass er die darauffolgenden Befehle ab Adresse 0x001 im Programmspeicher platzieren soll. Diese Stelle wird von INT0 angesprungenen.

Damit man nicht alle Interruptvektoren immer nachschlagen muss, sind in der Definitionsdatei m8def.inc einfach zu merkende Namen für die Adressen definiert. Statt 0x001 kann man z.B. einfach INT0addr schreiben. Das hat außerdem den Vorteil, dass man bei Portierung des Programms auf einen anderen AVR-Mikrocontroller nur die passende Definitionsdatei einbinden muss, und sich über evtl. geänderte Adressen für die Interruptvektoren keine Gedanken zu machen braucht.

Nun gibt es nur noch ein Problem: Beim Reset (bzw. wenn die Spannung eingeschaltet wird) wird das Programm immer ab der Adresse 0x000 gestartet. Deswegen muss an diese Stelle ein Sprungbefehl zum Hauptprogramm erfolgen, z.B. rjmp RESET um an die mit RESET: markierte Stelle zu springen.

Wenn man mehrere Interrupts verwenden möchte, kann man auch, anstatt jeden Interruptvektor einzeln mit .org an die richtige Stelle zu rücken, die gesamte Sprungtabelle ausschreiben:


.include "m8def.inc"

.org 0x000 ; kommt ganz an den Anfang des Speichers
rjmp RESET ; Interruptvektoren überspringen
; und zum Hauptprogramm
rjmp EXT_INT0 ; IRQ0 Handler
rjmp EXT_INT1 ; IRQ1 Handler
rjmp TIM2_COMP
rjmp TIM2_OVF
rjmp TIM1_CAPT ; Timer1 Capture Handler
rjmp TIM1_COMPA ; Timer1 CompareA Handler
rjmp TIM1_COMPB ; Timer1 CompareB Handler
rjmp TIM1_OVF ; Timer1 Overflow Handler
rjmp TIM0_OVF ; Timer0 Overflow Handler
rjmp SPI_STC ; SPI Transfer Complete Handler
rjmp USART_RXC ; USART RX Complete Handler
rjmp USART_DRE ; UDR Empty Handler
rjmp USART_TXC ; USART TX Complete Handler
rjmp ADC ; ADC Conversion Complete Interrupthandler
rjmp EE_RDY ; EEPROM Ready Handler
rjmp ANA_COMP ; Analog Comparator Handler
rjmp TWSI ; Two-wire Serial Interface Handler
rjmp SPM_RDY ; Store Program Memory Ready Handler

RESET: ; hier beginnt das Hauptprogramm

Hier ist es unbedingt nötig, bei unbenutzten Interruptvektoren statt des Sprungbefehls den Befehl reti reinzuschreiben. Wenn man einen Vektor einfach weglässt stehen die nachfolgenden Sprungbefehle sonst alle an der falschen Adresse im Speicher.


Hilft nur auch nicht weiter. Mein Problem: Die Adressierung. Uns wurde gesagt "BLOß KEINE FESTEN ADRESSEN FÜR DAS HAUPTPROGRAM!!!" und wenn ich das richtig sehe vergebe ich doch eine feste Adresse für das Hauptprogramm? Und für die Interrupts ebenso... Beispiel aus unserem Code:



.cseg

.org 0x0000
rjmp Progstart ; Interrupt Vektor für Reset

.org 0x0002
rjmp Extint1 ; Interrupt Vektor für externen Interrupt 1

.org 0x0018
rjmp TC1int ; Interrupt Vektor für Timer/Counter 1-Interrupt

.org 0x0045 ; Ende der Interrupt Vektor Tabelle

Progstart:
[...]

Extint1: ; ISR für externen Interrupt 1

reti

TC1int: ; ISR für Timer/Counter 1 Interrupt

reti

.exit


Beide Interrupts werden sonst nicht verwendet. Aber: Man benutzt doch einen Adressraum im Speicher für das Hauptprogramm, oder nicht? Von $0000 bis $0002 wenn ich das richtig sehe... Soll man aber doch nicht machen. Und warum werden Adressen für Interrupts ausgegeben die in ihrer Subroutine nichts tun auser an die Stelle zurück zu Springen an der sie benutzt werden ..

Ich bin verwirrt, helft mir :D
[/code]

Neutro
03.11.2008, 20:19
Meine ASM Kenntnisse sind zwar schon etwas eingerostet, aber wird ASM nicht so programmiert das die Adressen zu jedem Befehl vorangestellt sind
und dann hochgezählt werden, je nachdem ob ein oder zwei Byte Befehl?
dann wird doch der Sprung auf eine bestimmte Adresse ausgeführt oder auf ein Label.
Vielleicht ist das bei anderem Compilern aber schon wieder ganz anders als das System das wir damals hatten ( Mikroprozessor Intel 8085)

MfG

Neutro

BlackDevil
03.11.2008, 20:24
Ja du springst immer zu dem Label, relativer spring rjmp

Also


loop:
rjmp loop


Wäre eine Endlosschleife ... Aber das mit den Adressen versteh ich nich. Sage ich, dass mein Programm bei $0000 Anfängt und bei $0018 ein anderer Vektor beginnt heist ja das ich eine Begrenzte Kapazität an Speicher habe O.o Bzw mein Programm eben irgendwann i nden anders benutzten Bereich rein geht ^^

Neutro
03.11.2008, 20:45
Wenn die Speicheradressen fest vergeben sind kann es doch eigentlich nicht
passieren das eine Adresse 2x verwendet wird. Da muss dann doch der Compiler meckern oder?
Ein Programm besteht doch immer aus einer Schleife, wenn diese durchgearbeitet ist wird doch wieder an die Startadresse zurückgesprungen.
Die Unterprogramme werden dann doch unter das Hauptprogramm geschrieben. Dann kann sich da doch eigentlich nichts in die Quere kommen.
Vielleicht sehe ich das aber auch alles völlig falsch. Wäre gut wenn sich da noch ein erfahrener ASM Progger äußern könnte.

MfG

Neutro

BlackDevil
03.11.2008, 20:55
Jep wäre gut - ich bin nämlcih genauso Verwirrt...

0x0000
.
. PROGRAMM
.
0x0018
.
. INTERRUPT VEKTOR 1
.
0x02

Für mich sieht das nach Platz problem aus^^

PicNick
03.11.2008, 20:55
Das mit "keine festen Adressen" ist so gemeint, dass du dich immer nur auf symbolische Label beziehen sollst, und nicht "jmp 0x0043"

Natürlich hat ein Label letzlich eine feste adresse, sonst findet ihn ja keiner. Aber diese Adresse vergibt der Assembler, die geht dich nix an.

Vorteil, klaro: wenn du was einfügst oder löscht, ändern sich ja alle adressen danach. aber der Label (name) bleibt gleich.

die sache mit der Vector-Tabelle habe ich versucht, dort zu erklären:

https://www.roboternetz.de/wissen/index.php/AVR_Assembler_Einf%C3%BChrung#Struktur_eines_AVR-Maschinenprogrammes

Wenn du meinst, dass es auf einem µC Platzprobleme haben könntest, dann hast du recht :mrgreen:

BlackDevil
03.11.2008, 21:11
Also ist das obige so legal. DAs mit den Labels ist klar. Aber wenn ich sage von $0000 bis $0018 ist mein Programm, dann ist ja irgendwann schluss mit Platz ^^


danke für deinen Link. Seh ich das richtig das man einfach nur eine Adresse für die ISRs vergibt, das Programm aber an einer völlig anderen Stelle steht und für beides genügend Platz vom ASS reserviert wird?

Gock
03.11.2008, 21:20
Ich habe Dein eigentliches Problem noch nicht ganz verstanden, aber ich will es versuchen...

Uns wurde gesagt "BLOß KEINE FESTEN ADRESSEN FÜR DAS HAUPTPROGRAM!!!" und wenn ich das richtig sehe vergebe ich doch eine feste Adresse für das Hauptprogramm? Und für die Interrupts ebenso...

...könnte heißen, Du sollst nicht schreiben:
rjmp 0x56;
denn das wäre Blödsinn, weil sich die Adresse natürlich mit dem Code ändert, der dazwischen steht. Das kann nur der Compiler und der macht das sehr gut, wenn Du Labels (also variable Adressen) verwendest.


Aber: Man benutzt doch einen Adressraum im Speicher für das Hauptprogramm, oder nicht? Von $0000 bis $0002 wenn ich das richtig sehe...
Wenn ich mich recht errinnerne: Nein! Das sind die Adressen für die IRQs. DIe werden "hardwaremäßig" angesprungen, wenn der entsprechende IRQ ausgelöst wird. Mit .org reservierst Du zunächst Platz bis zum nächsten .org. Diesen Platz nutzt Du für den RJPM Befehl + Sprungsdresse, mehr nicht!. Würdest Du hier zeilenweise Code einfügen, dann würdest Du von einer IRQ Adresse zur nächsten schreiben und so ein riesen Chaos anrichten (sofern Deine Reservierung dem Standard entspricht). Deshalb wird bei größeren µCs 2Words und bei kleinerern nur 1Word reserviert, denn bei größeren ist der Adressraum größer.
Gruß

BlackDevil
03.11.2008, 21:25
.cseg

//.org 0x0000
//rjmp Progstart ; Interrupt Vektor für Reset

.org 0x0002
rjmp Extint1 ; Interrupt Vektor für externen Interrupt 1

.org 0x0018
rjmp TC1int ; Interrupt Vektor für Timer/Counter 1-Interrupt

.org 0x0045 ; Ende der Interrupt Vektor Tabelle

//Progstart:
[...]

Extint1: ; ISR für externen Interrupt 1

reti

TC1int: ; ISR für Timer/Counter 1 Interrupt

reti

.exit

Also stehen die mit // gekennzeichneten Stellen zwar im direkten zusammenhang, aber die Adressierung 0x000 und Progstart haben nichts mit einer Adressierung des Programmcodes, der ja dann beim nächsten Org zu ende wäre, zu tun richtig?

Gock
03.11.2008, 22:13
Du hast eine Gabe, Dich uneindeutig auszudrücken, aber ich würde sagen ja. Denn diese Adressen sind reserviert und hardwarecodiert. Immer, wenn der Reset IRQ ausgelöst wird, springt der Programpointer auf 0x0000 und das kann man nicht ändern. Er führt dann den Code aus, der dort steht, auch wenn da keiner steht. Dann geht alles in die Hose.
...denke ich... ;-)
Gruß

wkrug
04.11.2008, 08:29
Die ersten Speicherplätze im Programmteil werden tatsächlich Hardwaremässig angesteuert.
Ich hab das zu meinen Assemblerzeiten immer so gelöst, das ich die komplette Interrupttabelle für den Controller immer an den Anfang des Programms gestellt habe.
Wenn man jetzt noch für alle! möglichen Interrupts ein Label fabriziert in dem nur noch der Befehl RETI ausgeführt wird kann eigentlich nichts unvorhersehbares passieren.
Man muß auch darauf achten, das in der Vektortabelle die richtigen Sprungbefehle also JMP oder RJMP stehen, weil das von Controller zu Controller verschieden sein kann.

Das Hauptprogramm beginnt mit dieser Methode direkt nach der Interruptvektortabelle und kann somit nicht aus versehen durch einen Interrupt angesprungen werden.

Eine weitere praktizierte Methode ist, für nicht benutzte Interrups in die Tabelle einfach den Befehl RETI einzutragen, aber das gefällt mir persönlich nicht besonders.

Die nächste Möglichkeit ist nur einzelen Interruptvektoren mit einem vorangestellten .org zu versehen.
Das halt ich persönlich für etwas gefährlich, vertut man sich auch nur um eine Stelle landet der Controller im Nirwana. Möglich ist es aber.

Mehr Möglichkeiten fallen mir auf Anhieb nicht mehr ein. Praktiziert werden alle 3 Methoden.

BlackDevil
04.11.2008, 17:07
Die ersten Speicherplätze im Programmteil werden tatsächlich Hardwaremässig angesteuert.
Ich hab das zu meinen Assemblerzeiten immer so gelöst, das ich die komplette Interrupttabelle für den Controller immer an den Anfang des Programms gestellt habe.
Wenn man jetzt noch für alle! möglichen Interrupts ein Label fabriziert in dem nur noch der Befehl RETI ausgeführt wird kann eigentlich nichts unvorhersehbares passieren.
Man muß auch darauf achten, das in der Vektortabelle die richtigen Sprungbefehle also JMP oder RJMP stehen, weil das von Controller zu Controller verschieden sein kann.

Das Hauptprogramm beginnt mit dieser Methode direkt nach der Interruptvektortabelle und kann somit nicht aus versehen durch einen Interrupt angesprungen werden.

Eine weitere praktizierte Methode ist, für nicht benutzte Interrups in die Tabelle einfach den Befehl RETI einzutragen, aber das gefällt mir persönlich nicht besonders.

Die nächste Möglichkeit ist nur einzelen Interruptvektoren mit einem vorangestellten .org zu versehen.
Das halt ich persönlich für etwas gefährlich, vertut man sich auch nur um eine Stelle landet der Controller im Nirwana. Möglich ist es aber.

Mehr Möglichkeiten fallen mir auf Anhieb nicht mehr ein. Praktiziert werden alle 3 Methoden.

Genau so wurde uns das heute auch erklärt. Genau so. Danke.



.org $0

.org $2
rjmp Progstart


Beim Reset wird der Programm-Counter(-Zeiger) an die Adresse Null gesetzt, danach auf die Adresse zwei an die der Jump Befehl an das Label "Progstart steht" und danach kommt die Interrupt Tabelle die festgelegt im Handbuch steht und nach dieser Tabelle wird ein


.org $45 ; oder 40, willkür laut Prof


Rangestellt das den PCZ bzw den LInker an diese Adresse setzt an der dann auch das Programm startet. Ist nicht Pflicht sondern eigentlich aufgabe des Linkers, aber: Es kann passieren das man ausversehen ein Interrupt anspringt das man gar nicht anspringen wollte weil eben der PCZ gerade an der Adresse $18 ist an der eigntl ein Interrupt stehen müsste. BEsser die vollständige Tabelle vorranstellen und ruhe haben.

Darüberhinaus war die Verwirrung mit der Adressierung und benennung der Interrupts und das Anspringen von Leeren Subroutinen für die Katze. Das Musterprogramm was wir bekamen ist einfach das Grundgerüst für jeden der 3 Versuche das wir immer benutzen sollen. Wir müssen es nur erweitern, zB in V1A3 um einen Timer Interrupt der ja schon vorgegeben ist. In der Tabelle wie auch als Subroutine.


Und ja, ich weis das ich kein Erklärbär bin. Ich denke sehr Komplex (kompliziert) und kann das schlecht enddröseln ...