Assembler Assembler Einstieg

Lohnt sich Assembler zu lernen?

  • Ja

    Stimmen: 10 100,0%
  • Nein

    Stimmen: 0 0,0%

  • Umfrageteilnehmer
    10
Ich habe meine "delay" Routine mal überarbeitet..
Könnte das so aufem AVR funzen?


CodeBox Assembler
/* Verzögerungsschleife(n)
*/
#define CALC_DELAY_CYCLES( _ms_ )   ( ( F_CPU / F_DIV ) / _ms_ ) 
.def delayHigh = r24
.def delayLow = r25

delay1ms:
    ldi delayHigh , high( CALC_DELAY_CYCLES( 1 ) )
    ldi delayLow , low( CALC_DELAY_CYCLES( 1 ) )
    rcall delayLoop
    ret

delay10ms:
    ldi delayHigh , high( CALC_DELAY_CYCLES( 10 ) )
    ldi delayLow , low( CALC_DELAY_CYCLES( 10 ) )
    rcall delayLoop
    ret

delay100ms:
    ldi delayHigh , high( CALC_DELAY_CYCLES( 100 ) )
    ldi delayLow , low( CALC_DELAY_CYCLES( 100 ) )
    rcall delayLoop
    ret


/* Hier werden hauptsächlich die Taktzyklen verbraten..
*/
delayLoop:
    dec delayHigh
    dec delayLow
    brne delayLoop
    ret


Laut "Simulator" sieht das ganz okay aus..
 
Du verringerst in jedem Durchgang High und Low Teil, was das Ganze Sinnfrei macht.
Das BRNE greift somit nur auf das Resultat des letzten DEC. High wird zwar decrementiert was ein paar Takte frisst, aber mehr auch nicht. Was darin passiert wird nicht beachtet.

Möglich wäre es z.B. mit SBIW, falls einer der oberen 4 Registerpaare (R24:R25, R26:R27, R28:R29 oder R30:R31) noch frei sind.
 
Thomas hats bereits angedeutet, das BRNE reagiert nur auf das dec davor. An Stelle des ersten dec könnte auch ein nop stehen (was R24 sparen würde).
Du hast jetzt aber nur drei Subroutinen, die eine Zeitvernichtungsschleife aufrufen, welche dann einmal, zehnmal bzw hundertmal durchlaufen wird.
Der Aufruf der delay__MS kostet Takte. Das Beladen der Zählregister. Der Aufruf der eigentlichen delayLoop. Bei jedem Durchlauf wird (zweimal) inkrementiert, brne kostet bei jedem Sprung zwei Takte - beim letzten Durchlauf nur einen. Die beiden RETs zurück kosten auch Takte.

Statt Thomas' SBIW kannst Du natürlich auch zwei Schleifen verschachteln.

Zu Deinen Kommentaren aus #20: Der Stackpointer wird (sofern vorhanden) bei ALLEN Controllern beim Reset initialisiert. Aber nicht bei allen Controllern auf RAMEND, sondern (insbesondere bei älteren) manchmal auch auf 0x0000.
Der Stack wächst von hinten nach vorn, schrumpft von vorn nach hinten. Ein PUSH legt den Wert im SRAM ab und dekrementiert den Pointer, ein POP lädt den Wert aus dem SRAM und inkrementiert den Pointer. Analog dasselbe bei CALLs und RETs (die Adresse belegt aber zwei Bytes).
Bei einigen Controllern kannst Du ein Bein toggeln, indem Du eine "1" ins entsprechende PIN-Register-Bit schreibst. Beim M32 offensichtlich nicht.
Die Sprünge (bzw die Calls) unterscheiden sich in ihrer Reichweite, ihren Takten und auch dem benötigten Platz im Flash. Logischerweise kann nicht jeder Controller alle Instruktionen (ein Controller mit 512 Words Flash braucht kein EIJMP für gut vier Millionen Words...

Verzweigungen. Es gibt unbedingte:
  • RJMP springt relativ von der derzeitigen Position an eine feste Adresse (12Bit) (*)
  • JMP springt absolut auf eine feste Adresse (22Bit Adressraum)
  • IJMP verwendet den Inhalt des Z-Doppelregisters (16Bit Adressraum) als Sprungziel
  • EIJMP stellt dem Z noch ein drittes Register zur Seite (22Bit Adressraum)
  • RCALL(*), CALL, ICALL und EICALL machen dasselbe, legen lediglich vorher eine Rücksprungadresse auf den Stack -> Subroutine Calls eben
  • RET verwendet die obersten Bytes des Stack als Spungziel (Subroutine Return), RETI setzt nebenbei das I-Flag (aber nicht bei XMEGAS)
und bedingte. Bedingung ist hier immer ein Bit des SREG (siehe oben)
  • BRBS springt, wenn das Bit gesetzt ist
  • BRBC springt, wenn es gelöscht ist
Natürlich gibt es für jedes Bit je für die beiden Fälle eigene Mnemonics (beim Carry sogar doppelt) - aber vom Maschinencode her ist es BRBS/BRBC

Und es gibt Skips - unter bestimmten Bedingungen kann der nächste Befehl übersprungen werden:
  • CPSE vergleicht zwei Rechenregister, und "skippt" wenn gleich
  • SBRC und SBRS prüfen ein Bit in einem Rechenregister, und skippen wenn dieses gelöscht/gesetzt ist
  • SBIC und SBIS machen dasselbe mit einem Bit eines (der ersten 32) I/O-Register
(*) RJMP und RCALL sind eigentlich relative Sprünge - ein vorzeichenbehafteter 12Bit-Integer legt das jeweilige Ziel ausgehend von der aktuellen Position fest. Folgt man den derzeitigen Versionen des Studios könnte man also mit RJMP -10 zurückspringen, oder mit RCALL 42 vorwärts. Der entsprechende Opcode im AVR würde sogar gehen, ABER das Atmel Studio setzt den nicht so um.
Nur in der Online-Version des Instruction Sets findet sich der Hinweis:
In the assembler, labels are used instead of relative operands.
Eine dort verwendete Konstante akzeptiert der Assembler -> quasi als absolute Flashadresse. Insbesondere kann man also nicht einfach RJMP -1 für 'ne leere Endlosschleife schreiben, ohne Label.

Das nächste wären dann die Data Transfer Instructions… morgen oder so...
 
Vorschlag: C&P das mal als Resource. Sonst musst du irgendwann wieder googles kleinen Bruder spielen :D
Aber sehr gut @LotadaC :)

Klar, verschachteln der Zeit- und Stromvernichtungsschleifen wäre sinnvoller, spart die eh knappen Register. Bissl pushen und poppen macht hier ja auch nix aus, die Zeit soll ja eh verbrannt werden. Lustig wird es denn nur bei der Berechnung für andere Frequenzen etc, sollte es wirklich exakt sein.

Zum Stack, ich hatte es bei ihm probiert im Simulator. Der macht ohne das Init alles mögliche, nur nicht das was er soll. Hab ich auch noch nicht erlebt. Ohne Quelltext Änderung, nur Stack Init hinzugefügt, gehts.
 
**Update**


CodeBox Assembler

;


; asm versuche.asm

;

; Created: 09.09.2018 10:33:59

; Author : Jan

;

#include <m32def.inc>

/* Beginn des Datensegments

*/

/*****************************************************************/

.dseg

msg:

.db "Hello World!" , 0 , 0

/*****************************************************************/




/* Beginn des Kodesegments

*/

/*****************************************************************/

.cseg

.def tmp8 = r16




.equ F_CPU = 16000000 ; Systemfrequenz


.equ F_DIV = 1 ; Systemteiler




.equ LED_DDR_PORT = DDRD ; Led DatenRichtungsRegister


.equ LED_PORT = PORTD ; Led Port


.equ LED_bp = 4 ; Led Bit Position


/*****************************************************************/






initHardware:

/* StackPointer


*

* Der Stackpointer wird am Ende des RAM´s angelegt.

* Von dort aus wächst er je nach Bedarf nach "hinten"

* ( also Richtung Anfang des RAM´s ) raus.

*

* Einige Controller initalisieren den Stack schon automatisch richtig.

* Andere Initalisieren ihn auf Anfang vom RAM Bereich.

*

* Der ATmega32 ist noch keiner davon, der ihn am Ende des RAM´s

* initalisiert.

* Das heißt das müssen wir als erstes tun.

*

* Wichtig! Der Stack muss vor aufruf eines Unterprogrammes oder

* einen eingehenden / auftretenden Interrupt initalisiert werden.

*

*/

ldi tmp8 , high(ramend) ; Stackpointer initalisieren ; 1 Takt


out sph , tmp8 ; Addresse übergeben ; 1 Takt


ldi tmp8 , low(ramend) ; Stackpointer initalisieren ; 1 Takt


out spl , tmp8 ; Addresse übergeben ; 1 Takt


ldi tmp8 , 1<<LED_bp ; DatenRichtungsRegister Bit setzen ; 1 Takt


out LED_DDR_PORT , tmp8 ; Wert an DDRx übergeben ; 1 Takt

 
main:

rcall ledOn ; Welcher Sprung ist dafür jetzt besser geeignet? ; 3 Takte


rcall delay100ms ; 3 Takte


rcall ledOff ; 3 Takte


rcall delay100ms ; 3 Takte


rjmp main ; 2 Takte

 


/* Led einschalten

*/

/***********************************************/

ledOn:

cbi LED_PORT , LED_bp ; 2 Takte


ret ; 4 Takte


/***********************************************/




/* Led ausschalten

*/

/***********************************************/

ledOff:

sbi LED_PORT , LED_bp ; 2 Takte


ret ; 4 Takte


/***********************************************/






/* Verzögerungsschleifen

*

* Konfigurationen für die Verzögerungsschleife(n)

* var1ms = ( ( F_CPU / F_DIV ) / 1000000 )

*/

.equ preload1ms = ( ( F_CPU / F_DIV ) / 1000000 )

.def delayHigh = r24

.def delayLow = r25

/* Verzögerungsschleife 1 Millisekunde

*/

/***********************************************/

delay1ms:

ldi delayHigh , high( preload1ms ) ; 1 Takt


ldi delayLow , low( preload1ms ) ; 1 Takt


delay1msLoop:

sbiw delayHigh : delayLow , 1 ; Register -1 ; 2 Takte


brne delay1msLoop ; Solange "delay1msLoop" aufrufen bis delayHigh : delayLow == 0 ;


ret ; 4 Takte


/***********************************************/

/* Verzögerungsschleife 10 Millisekunden

*/

/***********************************************/

delay10ms:

ldi tmp8 , 10 ; 1 Takt


delay10msLoop:



rcall delay1ms ; 4 Takte


dec tmp8 ; 1 Takt


brne delay10msLoop ; 1 / 2 Takte


ret ; 4 Takte


/***********************************************/

/* Verzögerungsschleife 100 Millisekunden

*/

/***********************************************/

delay100ms:

ldi tmp8 , 100 ; 1 Takt


delay100msLoop :

rcall delay1ms ; 4 Takte


dec tmp8 ; 1 Takt


brne delay100msLoop ; 1 / 2 Takte


ret ; 4 Takte


/***********************************************/

 
Zuletzt bearbeitet:
Hallo,

ups ... ist der Thread schon lang ...

Also grundsätzlich ...
In Assembler gibt es sehr viele teilweise sehr stark unterschiedliche Dialekte. Wenn man sich bei einem Prozessor mit dem Befehlssatz auskennt, dann heißt es nicht das es bei einem anderen dann auch so ist. Teilweise sind die so unterschiedlich wie C und Basic :eek:
Man findet zwar überall ähnliche Befehle/Strukturen, aber bei einem heißt es MOV (move) und bei nem anderen LD (load).
Also dann zB MVI (move imediately) oder LDI (load imediately).
Manche arbeiten mit Prefetch der Befehle und Cache (zB Intel, AMD, ARM) und andere direkt (AVR, PIC, ...). Das ändert manche Sachen im Programm.
Manche haben User/System-Abtrennungen und bei manchen läuft alles unter einem Kontext.
Alleine wenn der Controller DMA kann, ändert sich schon einiges. Dann ist das quasi als wenn das System mehrere CPUs hat die unabhängig voneinander im Speicher arbeiten. Zum Bsp liefert dann der AD-Wandler oder der UART selber Daten ins RAM in einen Speicherbereich. In der Zeit kann dann die CPU selber nicht auf diesen Bereich schreiben (geblockt). So etwas muß man beim Timing beachten.

Aber grundsätzlich gilt: Wenn man einmal in die Denkweise hinein gekommen ist, dann wird man schnell mit anderen Prozessoren zurecht kommen.
Alleine für die hardwarenahe Sichtweise lohnt es sich, das mal bei einem einfachen Prozessor anzusehen.

Gruß
Dino
 


CodeBox Assembler
.equ F_CPU = 16000000 ; Systemfrequenz
.equ F_DIV = 1 ; Systemteiler
...
.equ preload1ms = ( ( F_CPU / F_DIV ) / 1000000 )
16000000 / 1 / 1000000 = 16


CodeBox Assembler
delay1msLoop:
sbiw delayHigh : delayLow , 1 ; Register -1 ; 2 Takte 
brne delay1msLoop ; Solange "delay1msLoop" aufrufen bis delayHigh : delayLow == 0 ; 
ret ; 4 Takte
Die Schleife wird also 16 mal durchlaufen.
brne braucht, wenn ich mich recht erinnere, bei ausgeführtem branch 2 Takte, macht mit sbiw zusammen 4 Takte pro Schleifendurchlauf.
16 * 4 = 64
64 Takte entsprechen bei 16 MHz 4 µs.
Deine Millisekunde dauert also nur vier Mikrosekunden (plus 250 ns für den ret).
 
Ok, in #19 hatte ich alle Recheninstruktionen (bzw alle, Instruktionen, die das SREG manipulieren) genannt,
in #23 alle Sprünge (insbesondere auch die, die durch das SREG bedingt springen.

Aber wie bekommt man nun Daten in die Rechenregister rein (bzw aus ihnen raus)?
Die Schnittstelle zur realen Welt sind die I/O-Register - Input/Output.
In lädt den Inhalt eines I/O-Registers in ein Rechenregister, Out kopiert den Inhalt eines Rechenregisters in ein I/O-Register. Adressieren können beide nur bis 0x3F.

Für den Zugriff auf das SRAM (wobei hier meist die Rechenregister adresstechnisch mit eingebunden sind (manchmal sogar ein Abbild der nichtflüchtigen Speicher) gibt es:
  • Lds/Sds lädt vom/speichert direkt auf die angegebene SRAM-Adresse
  • Ld/St lädt vom/speichert indirekt auf die, durch eines der Doppelregister X,Y oder Z referenzierte SRAM-Adresse. Dabei kann außerdem der Pointer Pre-decrementiert oder Post-inkrementiert werden.
  • Ldd/Std nutzt analog das Y oder Z als Pointer, zusätzlich wird ein konstanter Offset (6Bit) dazuaddiert. Zusammen mit LD/ST kann man so geschickt mehrere Listen/Arrays/… gleichzeitig verarbeiten, ohne den Pointer umladen zu müssen. Insbesondere in Schleifen...
  • Push kopiert den Inhalt eines Rechenregisters ins SRAM, adressiert durch den Stackpointer (welcher dabei automatisch dekrementiert), Pop macht das Gegenteil davon.
  • Den XMegas stehen außerdem noch Las, Lac, Lat und Xch zur Verfügung. Damit können Bits in SRAM-Registern direkt gesetzt/gelöscht/getoggelt werden. Hintergrund ist, daß dort fast alle Peripherals außerhalb des konventionellen I/O-Space liegen. In und Out greifen also nicht, Sbi/Cbi/Sbic/Sbis erst recht nicht.
Für den Zugriff auf den Flash gibt es (E)Lpmund Spm, referenziert wird die Adresse durch das Z-Doppelregister.

Bei den Rechenregistern selbst:
  • kann mit Ldi eine Konstante geleden werden (welche quasi mit dem Ldi im Flash abgelegt ist)
  • mit Mov der Inhalt eines Registers in ein anderes kopiert werden
  • mit Movw dasselbe mit je zwei Registern (also 16Bit)
  • kopiert BST ein Bit aus einem Rechenregister in's T-Flag des SREG, Bld lädt dieses Flag in ein Rechenregister-Bit
Bei den Bit und Bit-Test Instruktionen fehlen noch:
  • Swap - tauscht die Nibble eines Rechenregisters
  • Sbi/Cbi - setzt/löscht ein Bit eines I/O-Registers direkt - ohne Umweg über ein Rechenregister (nur die ersten 0x1F I/O-Register sind adressierbar)
  • Bset/Bclr - setzt/löscht direkt ein Bit im SREG (hier gibt's auch wieder für jedes Bit zwei separate Mnemonics, klar)

Zur "Steuerung" der MCU selbst gibt es dann noch folgende Instruktionen:
  • Nop - macht einen Takt lang nichts
  • Sleep - aktiviert den, im entsprechenden I/O-Register ausgewählten Sleep-Mode
  • Wdr - setzt den Watchdog-Counter zurück
  • Break -ermöglicht einem Debugger Zugriff auf den Controller
Mehr sinds nicht...
 
Deine Millisekunde dauert also nur vier Mikrosekunden (plus 250 ns für den ret).
Ja, darauf hatte ich zumindest dezent mit:
Du hast jetzt aber nur drei Subroutinen, die eine Zeitvernichtungsschleife aufrufen, welche dann einmal, zehnmal bzw hundertmal durchlaufen wird.
hingewiesen...
Zum Stack, ich hatte es bei ihm probiert im Simulator.
Mir gings darum, daß jeder(!) AVR seinen SP beim Reset initialisiert - wohin ist 'ne ganz andere Frage, ob man dann ggf woandershin reinitialisieren muß auch. Aber
* Einige Controller initalisieren den Stack schon automatisch.. * Der ATmega32 ist noch keiner davon...
Ist eben nicht korrekt.
Man muß den Stack auch nicht zwingend ans Ende packen, man kann auch mehrere Stacks damit verwalten - unter ASM bestimmst Du selbst die Regeln...

Zum Listing aus #25: das ist C&P direkt aus dem Studio. Die Leerzeilen kann man umgehen, indem man den Code erstmal in 'ne leere Textdatei oder so postet, und von dort nochmal kopiert. Hatte @Dirk schonmal auf das Problem (sicher ausgehend vom Studio) hingewiesen...

Bei den aktuellen Studio-Versionen muß die Controllerdefinitionsdatei nicht mehr extra inkludiert werden - Du mußt eh den Controller bei den Projekteinstellungen festlegen (und kannst ihn da auch ändern (was bei ASM aber weniger Sinn macht als bei Hochsprachen)). Wenn dort ein anderer Controller gewählt ist, gibt's natürlich 'ne Fehlermeldung. Ich vermerke aber den Controller als Kommentar.

Zeitvernichtungsschleifen berechne ich lieber individuell. Üblicherweise verwende ich die nur bei kurzen Wartezeiten - bei längeren dann eher über 'ne timerbasierte Zeitbasis. Warum soll der Controller zichtausend Takte warten? Da kann er Doch vielleicht auch was anderes zwischendurch machen... (aber als Übung... klar...)

In Zeile 127 fragst Du immer noch, welchen Sprung Du verwenden sollst. Wie gesagt: die unterscheiden sich im Flashbedarf und im Zeitverbrauch. Außerdem in der Reichweite. Was davon für Dich relevant ist, entscheidest DU.

P.S.: bei den relativen Sprüngen kann man auch rückwärts (über 0x0000 hinweg) ans Ende des Flash springen (wieviele Bits hat der Stackpointer ;)) . Z.B. aus der IVT heraus...

Alleine wenn der Controller DMA kann, ändert sich schon einiges. Dann ist das quasi als wenn das System mehrere CPUs hat die unabhängig voneinander im Speicher arbeiten. Zum Bsp liefert dann der AD-Wandler oder der UART selber Daten ins RAM in einen Speicherbereich. In der Zeit kann dann die CPU selber nicht auf diesen Bereich schreiben (geblockt). So etwas muß man beim Timing beachten.
Sowas ähnliches hast Du auch schon bei den non-XMegas - mit der Peripherie. Die arbeitet ja auch "neben" der CPU. Jan hat ja selbst erlebt, daß ans UART-Transmit-Register geschriebene Daten ins Nirvana fallen, wenn es nicht leer ist. Ebenso kann man den refresh des ADC-Result-Doppelregisters blockieren (indem man ADCL ausliest und zulange das ADCH nicht)., usw...
 
Ich habe hier gerade mein erstes Projekt neben mir liegen "LED Kreisel". Den habe ich schon mal in C mit einem PWM Lauflicht ( Knight Rider ) programmiert.

Wie gehe ich vor, wenn ich jetzt in ASM ne Software PWM erzeugen will? Klar.. erstmal nen Timer initalisieren und dann?
 
Mit Timer wäre es doch Hardware PWM, oder eine Mischung aus beidem.

Auf jeden Fall gehts mit Interrupts weiter. Speziell erstmal die Interrupt Vector Table verstehen und einbinden ;)
Ist aber halb so wild.
Öffne die .inc Datei für deinen Controller und suche hiernach:


CodeBox Assembler
; ***** INTERRUPT VECTORS ************************************************
.equ    INT0addr    = 0x0002    ; External Interrupt Request 0
.equ    INT1addr    = 0x0004    ; External Interrupt Request 1
...

Denn machst du in deinem Code:


CodeBox Assembler
.ORG 0x0000 RJMP OnReset
.ORG INT0addr RJMP OnInt0
.ORG INT1addr RETI
...

Die Zeilen mit RETI brauchst du eigentlich nicht, ich füge die immer mit ein, stört ja nicht.

Warum ist einfach. Der Controller wacht auf und fängt bei Adresse 0x0000 an Befehle abzuarbeiten, kennst du ja. Hast du aber Interrupts aktiv ist das quasi so wie ein Reset, nur dass er nicht zu 0x0000 springt sondern z.B. zu 0x0002 (hier INT0addr).
In der IVT stehen nur RJMP drin, oder direkt RETI falls unbenutzt. Wichtig ist nur dass du die angesprungene Routine immer mit RETI abschließt, sonst wars das mit Interrupts ;) (jaja, geht auch anders, lassen wir das jetzt mal... Verwirrt sonst nur.)
Einzige Ausnahme ist natürlich das OnReset was deine Hauptschleife startet. Die soll sich ja nicht terminieren, sonst explodiert der Mond, und das willst du nicht.

Streng genommen, wenn man die komplette Tabelle übernimmt könnte man sich die ganzen .ORGs sparen (außer dem ersten), aber du gerätst möglicherweise komplett durcheinander wenn du mal einen anderen Chip nutzen willst. Böse Fehler, und davon jede Menge. Selbst erlebt. Lieber gleich ordentlich.

Auf jeden Fall würde er jetzt wenn INT0 ausgelöst wird in die Routine OnInt0 springen.

SEI (Interrupts global aktivieren) nicht vergessen, genau wie das Aktivieren des jeweiligen Interrupts selbst.
Vielleicht besser erstmal mit nem Taster testen bevor es gleich an den Counter geht.

EDIT: Ich vergaß:
Nach der IVT bitte noch ein


CodeBox Assembler
.ORG INT_VECTORS_SIZE

Dann startet dein Code direkt nach der Tabelle.
 
Warum ist einfach. Der Controller wacht auf und fängt bei Adresse 0x0000 an Befehle abzuarbeiten, kennst du ja. Hast du aber Interrupts aktiv ist das quasi so wie ein Reset, nur dass er nicht zu 0x0000 springt sondern z.B. zu 0x0002 (hier INT0addr).
.ORG 0x0000 RJMP OnReset.ORG INT0addr RJMP OnInt0.ORG INT1addr RETI...
Und das beim "Anfang" anfange ( 0x0000) springt er in den nächsten Eintrag in der Tabelle und da findet er nichts und hängt sich auf..?
Verstehe ich das jetzt richtig.. Wenn ich die IVT nicht richtig eintrage
 
Die Schleife wird also 16 mal durchlaufen.
brne braucht, wenn ich mich recht erinnere, bei ausgeführtem branch 2 Takte, macht mit sbiw zusammen 4 Takte pro Schleifendurchlauf.
16 * 4 = 64
64 Takte entsprechen bei 16 MHz 4 µs.
Deine Millisekunde dauert also nur vier Mikrosekunden (plus 250 ns für den ret).

Stimmt! Es müssen natürlich 16k Takte sein..
 
Der Chip weiß natürlich die Adresse an die er beim Interrupt springen muss. PowerOnReset ist immer 0x0000 (daher auch ohne Namen). Wird jetzt irgendein Interrupt ausgelöst, sei es nun INT0 oder ADC oder UART oder oder oder springt er an die jeweilige Adresse in der IVT, wie beim PowerOnReset, nur in dem Fall halt nicht an 0x0000 sondern bei INT0 an INT0addr, also 0x0002 (hier). Daher kannst du hier auch nur einen Befehl rein setzen. Es gibt Ausnahmen, der Einfachheit halber lassen wir das jetzt aber außer Acht.
 
  • Like
Reaktionen: Janiiix3
Der Chip weiß natürlich die Adresse an die er beim Interrupt springen muss. PowerOnReset ist immer 0x0000 (daher auch ohne Namen). Wird jetzt irgendein Interrupt ausgelöst, sei es nun INT0 oder ADC oder UART oder oder oder springt er an die jeweilige Adresse in der IVT, wie beim PowerOnReset, nur in dem Fall halt nicht an 0x0000 sondern bei INT0 an INT0addr, also 0x0002 (hier). Daher kannst du hier auch nur einen Befehl rein setzen. Es gibt Ausnahmen, der Einfachheit halber lassen wir das jetzt aber außer Acht.
Also ich schreibe jetzt in diese Tabelle "nur" den Interrupt Vektor den ich zu meinem aktuellen Projekt benötige? Das reicht dann?
Wo genau müssen diese Definitionen stehen? .cseg .dseg.. egal?
 
Rate mal. Wo steht ausführbarer Code? ;)
Und ja, rein theoretisch reicht es wenn du nur die angibst die du brauchst. Ich geb immer alle an, dann halt mit RETI statt RJMP. Hat den Vorteil, aktiviert man einen Interrupt, hat den aber in der IVT vergessen zersemmelt es einem nicht das ganze Programm.
 
Und ja, rein theoretisch reicht es wenn du nur die angibst die du brauchst. Ich geb immer alle an, dann halt mit RETI statt RJMP.
Würde das nicht zusätzlich Speicher in Anspruch nehmen?
 
Nu tu nich so alsob dich das vorher gestört hat. Was meinste was C macht? :D
Aber ja, beim Mega64 glaube ich 70 Bytes. Bei den Tinys wesentlich weniger (klar, haben ja auch weniger Interrupt Quellen). Da du aber noch fern ab von der Grenze des nutzbaren Speichers bist spielt das keine Rolle. Das fällt dann wenn überhaupt als letzter Schritt, Stichwort Optimierung, eine Rolle.
 
Es geht mir einfach darum ob es Speicher in Anspruch nimmt oder nicht, hat nichts mit meckern zu tun.

Andere Frage..
Kann ich auch Funktionen bauen? Welchen ich Parameter übergeben kann? So wie bei einem Macro in Assembler?
 
Power On Reset (bzw jeder Reset) ist genau genommen kein Interrupt. Wenn der Reset freigegeben wird, werden (fast) alle I/O-Register initialisiert, ebenso der Programcounter.
Wenn die Bootrst-Fuse aktiviert ist aber nicht auf 0x0000.
Ebenso können die Interruptvektoren verschoben werden, unabhängig von Bootrst.

Jede Interruptquelle ist mit einer festen Adresse "verbunden" - ist der Interrupt selbst scharf, getriggert und I in SREG gesetzt, wird der laufende Befehl beendet, und dann automatisch I in SREG gelöscht, die Rücksprungadresse auf den Stack gepusht (2 Bytes) und die Interrupt-Adresse in den Programcounter geladen. Sind vier Takte. Als nächstes wird also das ausgeführt, was in der IVT steht. Das muß nicht unbedingt ein Sprung in eine ISR sein, meist ist dort aber nicht genug Platz (je nachdem, welche IRQs Du verwendest, und wie kurz die ISR ist aber vielleicht doch).
Es geht mir einfach darum ob es Speicher in Anspruch nimmt oder nicht,
Wenn Du vom Reseteinsprung irgendwo hinter die IVT ("IntVectorsSize") springst, wird der Code in der IVT selbst nur dann ausgeführt, wenn er angesprungen wird. Entweder durch einen (von Dir) aktivierten IRQ, oder durch einen (von Dir) programmierten Sprung.
Sonst ist der Code dort … verschwendet. Strenggenommen kannst Du dort noch Konstanten ablegen, die irgendwo anders mit LPM geladen werden sollen.
Auf der anderen Seite stellt sich die Frage, ob Dir genau diese paar Words Programmspeicher am Ende wirklich fehlen.
Eher kann es notwendig sein, eine ISR dort vorn zu platzieren - wegen der Reaktionszeit eines Interruptes (wie gesagt: nach vier Takten wird der eigentliche Interruptvektor ausgeführt; steht dort ein RJMP oder JMP, kommen nochmal zwei bzw drei Takte dazu. Dir fehlen eher zwei bis drei Takte Reaktionszeit bei einem IRQ, als ein paar Words im Gesamt-Flash... naja, beim Tiny4/9 oder so vielleicht...
Es kann also durchaus sinnig sein, die Interrupt-Service-Routinen in der Nähe der IVT anzulegen, und sie statt mit JMP mit RJMP anzuspringen.
(Gilt ja eh erst für Controller mit viel Flash. Trotzdem kann man das dann noch auf die Spitze treiben - die IVT ist ja dann zwei Words "breit" (damit JMP reinpaßt), RJMP belegt aber nur ein Word. Man kann also jedesmal vor dem RJMP einen anderen single-Word-Mnemonic platzieren (wobei die einzigen Doppelwort-Instructions ja eh CALL, JMP, LDS und STS sind)
Was meinste was C macht?
C (also das Studio) läßt aus der IVT "irgendwohin" rausspringen. Meiner Meinung nach hinter den Code der Main(). Vermutlich werden dort ggfs. auch sämtliche verwendete ISRs platziert (hatte ich damals nicht mit drin), nicht verwendete landen auf einer gemeinsamen Adresse, von der ein Sprung zum Reset-Vektor erfolgt.
Hat gegenüber einfachen RETIs den Vorteil, daß man einen unbeabsichtigt scharfgemachten (=Programmfehler) auch erkennt.
Verwendest Du normalen Code in der IVT, erkennst Du diesen Fehler natürlich auch - wann Du ihn findest, ist 'ne andere Frage.
Kann ich auch Funktionen bauen?
Klar, hab doch oben bereits alle Operationen genannt. Mit den vier CALLs kannst Du die Adresse einer Subroutine anspringen, mit RET zurückkehren.
Um die Übergabe irgendwelcher Parameter oder Ergebnisse mußt Du Dich selbst kümmern.
Aber meist willst Du Dich doch mit derlei Overhead gar nicht rumschlagen - das hier ist Assembler, der AVR hat zweiunddreißig Rechenregister. Mußt Du wirklich alles ständig zwischen SRAM und Rechenregistern hin und herschaufeln?
 

Ü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)